diff --git a/compiler/rustc_codegen_gcc/.cspell.json b/compiler/rustc_codegen_gcc/.cspell.json deleted file mode 100644 index 556432d69a41b..0000000000000 --- a/compiler/rustc_codegen_gcc/.cspell.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "allowCompoundWords": true, - "dictionaries": ["cpp", "rust-extra", "rustc_codegen_gcc"], - "dictionaryDefinitions": [ - { - "name": "rust-extra", - "path": "tools/cspell_dicts/rust.txt", - "addWords": true - }, - { - "name": "rustc_codegen_gcc", - "path": "tools/cspell_dicts/rustc_codegen_gcc.txt", - "addWords": true - } - ], - "files": [ - "src/**/*.rs" - ], - "ignorePaths": [ - "src/intrinsic/archs.rs", - "src/intrinsic/old_archs.rs", - "src/intrinsic/llvm.rs" - ], - "ignoreRegExpList": [ - "/(FIXME|NOTE|TODO)\\([^)]+\\)/", - "__builtin_\\w*" - ] -} diff --git a/compiler/rustc_codegen_gcc/.github/workflows/ci.yml b/compiler/rustc_codegen_gcc/.github/workflows/ci.yml index fa9535a3729c3..529003fc384a0 100644 --- a/compiler/rustc_codegen_gcc/.github/workflows/ci.yml +++ b/compiler/rustc_codegen_gcc/.github/workflows/ci.yml @@ -26,16 +26,17 @@ jobs: - { gcc: "gcc-15.deb" } - { gcc: "gcc-15-without-int128.deb" } commands: [ - "--std-tests", + "--std-tests --alloc-tests", # FIXME: re-enable asm tests when GCC can emit in the right syntax. # "--asm-tests", "--test-libcore", "--extended-rand-tests", - "--extended-regex-example-tests", + "--extended-regex-example-tests --test-libcore-doctests", "--extended-regex-tests", "--test-successful-rustc --nb-parts 2 --current-part 0", "--test-successful-rustc --nb-parts 2 --current-part 1", "--projects", + "--gcc-asm-tests --test-release-libcore", ] steps: @@ -52,9 +53,6 @@ jobs: # `llvm-14-tools` is needed to install the `FileCheck` binary which is used for asm tests. run: sudo apt-get install ninja-build ripgrep llvm-14-tools llvm - - name: Install rustfmt & clippy - run: rustup component add rustfmt clippy - - name: Download artifact run: curl -LO https://github.com/rust-lang/gcc/releases/latest/download/${{ matrix.libgccjit_version.gcc }} @@ -88,16 +86,17 @@ jobs: - name: Check formatting run: ./y.sh fmt --check - - name: clippy - run: | - cargo clippy --all-targets -- -D warnings - cargo clippy --all-targets --no-default-features -- -D warnings - cargo clippy --manifest-path build_system/Cargo.toml --all-targets -- -D warnings + - name: Check todo + run: ./y.sh check-todo + + - name: Check lints + run: ./y.sh clippy - name: Build run: | ./y.sh build --sysroot ./y.sh test --cargo-tests + CARGO_TEST_FLAGS="-Zmir-preserve-ub" ./y.sh test --cargo-tests -- mir_preserve_ub_empty_switch - name: Run y.sh cargo build run: | @@ -127,13 +126,6 @@ jobs: - uses: actions/checkout@v4 - run: python tools/check_intrinsics_duplicates.py - spell_check: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - - uses: crate-ci/typos@v1.32.0 - - uses: streetsidesoftware/cspell-action@v7 - build_system: runs-on: ubuntu-24.04 steps: diff --git a/compiler/rustc_codegen_gcc/.github/workflows/failures.yml b/compiler/rustc_codegen_gcc/.github/workflows/failures.yml index 2c9e4950706b2..52e96726129b3 100644 --- a/compiler/rustc_codegen_gcc/.github/workflows/failures.yml +++ b/compiler/rustc_codegen_gcc/.github/workflows/failures.yml @@ -98,7 +98,14 @@ jobs: if: matrix.libgccjit_version.gcc != 'libgccjit12.so' id: tests run: | - ${{ matrix.libgccjit_version.env_extra }} ./y.sh test --release --clean --build-sysroot --test-failing-rustc ${{ matrix.libgccjit_version.extra }} 2>&1 | tee output_log + # Without this, `tee` masks the exit status of `y.sh test`. + set -o pipefail + status=0 + ${{ matrix.libgccjit_version.env_extra }} ./y.sh test --release --clean --build-sysroot --test-failing-rustc ${{ matrix.libgccjit_version.extra }} 2>&1 | tee output_log || status=$? + # This suite runs the tests known to fail, so only a build system error must fail the job. + if [ "$status" -ne 0 ] && [ "$status" -ne 2 ]; then + exit "$status" + fi rg --text "test result" output_log >> $GITHUB_STEP_SUMMARY - name: Run failing ui pattern tests for ICE @@ -106,7 +113,14 @@ jobs: if: matrix.libgccjit_version.gcc != 'libgccjit12.so' id: ui-tests run: | - ${{ matrix.libgccjit_version.env_extra }} ./y.sh test --release --test-failing-ui-pattern-tests ${{ matrix.libgccjit_version.extra }} 2>&1 | tee output_log_ui + # Without this, `tee` masks the exit status of `y.sh test`. + set -o pipefail + status=0 + ${{ matrix.libgccjit_version.env_extra }} ./y.sh test --release --test-failing-ui-pattern-tests ${{ matrix.libgccjit_version.extra }} 2>&1 | tee output_log_ui || status=$? + # This suite runs tests that fail, so only a build system error must fail the job here. + if [ "$status" -ne 0 ] && [ "$status" -ne 2 ]; then + exit "$status" + fi if grep -q "the compiler unexpectedly panicked" output_log_ui; then echo "Error: 'the compiler unexpectedly panicked' found in output logs. CI Error!!" exit 1 diff --git a/compiler/rustc_codegen_gcc/.github/workflows/stdarch.yml b/compiler/rustc_codegen_gcc/.github/workflows/stdarch.yml index 66f30b147b4c0..17d6449c85e08 100644 --- a/compiler/rustc_codegen_gcc/.github/workflows/stdarch.yml +++ b/compiler/rustc_codegen_gcc/.github/workflows/stdarch.yml @@ -21,7 +21,7 @@ jobs: fail-fast: false matrix: cargo_runner: [ - "sde -future -rtm_mode full --", + "sde -cpuid-in /home/runner/work/rustc_codegen_gcc/rustc_codegen_gcc/tests/cpuid.def -rtm_mode full --", "", ] @@ -42,8 +42,14 @@ jobs: - name: Install more recent binutils run: | echo "deb http://archive.ubuntu.com/ubuntu plucky main universe" | sudo tee /etc/apt/sources.list.d/plucky-copies.list - sudo apt-get update + sudo apt-get update -o Acquire::Retries=3 sudo apt-get install binutils + installed="$(dpkg-query --showformat='${Version}' --show binutils)" + echo "Installed binutils: $installed" + if dpkg --compare-versions "$installed" lt "2.44"; then + echo "::error::binutils upgrade failed (got $installed, need >= 2.44); the apt fetch probably failed" + exit 1 + fi - name: Install Intel Software Development Emulator if: ${{ matrix.cargo_runner }} @@ -51,10 +57,9 @@ jobs: mkdir intel-sde cd intel-sde version=10.8.0-2026-03-15 - url_path=915934 dir=sde-external-$version-lin file=$dir.tar.xz - wget https://downloadmirror.intel.com/$url_path/$file + wget http://ci-mirrors.rust-lang.org/$file tar xvf $file sudo mkdir /usr/share/intel-sde sudo cp -r $dir/* /usr/share/intel-sde @@ -90,14 +95,15 @@ jobs: - name: Run stdarch tests if: ${{ !matrix.cargo_runner }} run: | - CHANNEL=release TARGET=x86_64-unknown-linux-gnu CG_RUSTFLAGS="-Ainternal_features" ./y.sh cargo test --manifest-path build/build_sysroot/sysroot_src/library/stdarch/Cargo.toml + # FIXME: remove --skip test_tile_ and --skip --skip test__tile when it's implemented. + ./y.sh test --release --stdarch-tests -- --skip test_tile_ --skip test__tile - name: Run stdarch tests if: ${{ matrix.cargo_runner }} run: | # FIXME: these tests fail when the sysroot is compiled with LTO because of a missing symbol in proc-macro. - # FIXME: remove --skip test_tile_ when it's implemented. - STDARCH_TEST_SKIP_FUNCTION="xsave,xsaveopt,xsave64,xsaveopt64" STDARCH_TEST_EVERYTHING=1 CHANNEL=release CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER="${{ matrix.cargo_runner }}" TARGET=x86_64-unknown-linux-gnu CG_RUSTFLAGS="-Ainternal_features" ./y.sh cargo test --manifest-path build/build_sysroot/sysroot_src/library/stdarch/Cargo.toml -- --skip rtm --skip tbm --skip sse4a --skip test_tile_ + # FIXME: remove --skip test_tile_ and --skip --skip test__tile when it's implemented. + STDARCH_TEST_SKIP_FUNCTION="xsave,xsaveopt,xsave64,xsaveopt64" STDARCH_TEST_EVERYTHING=1 CHANNEL=release CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER="${{ matrix.cargo_runner }}" TARGET=x86_64-unknown-linux-gnu CG_RUSTFLAGS="-Ainternal_features" ./y.sh cargo test --manifest-path build/build_sysroot/sysroot_src/library/stdarch/Cargo.toml -- --skip rtm --skip tbm --skip sse4a --skip test_tile_ --skip test__tile # Summary job for the merge queue. # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! diff --git a/compiler/rustc_codegen_gcc/.gitignore b/compiler/rustc_codegen_gcc/.gitignore index 8f73d3eb972a0..13bd0d0ffde9b 100644 --- a/compiler/rustc_codegen_gcc/.gitignore +++ b/compiler/rustc_codegen_gcc/.gitignore @@ -7,7 +7,7 @@ perf.data.old *.events *.string* gimple* -*asm +*_asm res test-backend projects @@ -20,4 +20,5 @@ llvm build_system/target config.toml build -rustlantis \ No newline at end of file +rustlantis +stuff/ diff --git a/compiler/rustc_codegen_gcc/CONTRIBUTING.md b/compiler/rustc_codegen_gcc/CONTRIBUTING.md index 8f81ecca445a8..c5c2a783b1ee7 100644 --- a/compiler/rustc_codegen_gcc/CONTRIBUTING.md +++ b/compiler/rustc_codegen_gcc/CONTRIBUTING.md @@ -112,7 +112,7 @@ Full list of debugging options can be found in the [README](Readme.md#env-vars). ### Code Style Guidelines - Follow Rust standard coding conventions -- Ensure your code passes `rustfmt` and `clippy` +- Ensure your code passes `rustfmt` and `clippy` (you can run them with `y.sh fmt` and `y.sh clippy`) - Add comments explaining complex logic, especially in GCC interface code ## Additional Resources diff --git a/compiler/rustc_codegen_gcc/Cargo.lock b/compiler/rustc_codegen_gcc/Cargo.lock index 7ce94b58c0516..c174628d0d188 100644 --- a/compiler/rustc_codegen_gcc/Cargo.lock +++ b/compiler/rustc_codegen_gcc/Cargo.lock @@ -31,9 +31,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", "windows-sys", @@ -56,18 +56,18 @@ dependencies = [ [[package]] name = "gccjit" -version = "3.3.0" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b73d18b642ce16378af78f89664841d7eeafa113682ff5d14573424eb0232a" +checksum = "6d85b5754389edaad832ba320709a25086b3081a8c6c0fab2322965e5fb512b3" dependencies = [ "gccjit_sys", ] [[package]] name = "gccjit_sys" -version = "1.3.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee689456c013616942d5aef9a84d613cefcc3b335340d036f3650fc1a7459e15" +checksum = "e081669728b490723537f9def7eb674b7c9acd8de0b92ad4f4abf5f5cc75ea4b" dependencies = [ "libc", ] @@ -117,15 +117,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.168" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "memchr" @@ -194,9 +194,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", @@ -216,9 +216,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.20.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom", @@ -311,78 +311,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.59.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-targets" -version = "0.52.6" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows-link", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/compiler/rustc_codegen_gcc/Cargo.toml b/compiler/rustc_codegen_gcc/Cargo.toml index ac5e94b9454e2..02be6d56c2310 100644 --- a/compiler/rustc_codegen_gcc/Cargo.toml +++ b/compiler/rustc_codegen_gcc/Cargo.toml @@ -20,11 +20,11 @@ default = ["master"] [dependencies] object = { version = "0.39.0", default-features = false, features = ["std", "read"] } tempfile = "3.20" -gccjit = { version = "3.3.0", features = ["dlopen"] } +gccjit = { version = "6.1.0", features = ["dlopen"] } #gccjit = { git = "https://github.com/rust-lang/gccjit.rs", branch = "error-dlopen", features = ["dlopen"] } # Local copy. -#gccjit = { path = "../gccjit.rs", features = ["dlopen"] } +# gccjit = { path = "../gccjit.rs", features = ["dlopen"] } [dev-dependencies] boml = "0.3.1" diff --git a/compiler/rustc_codegen_gcc/Readme.md b/compiler/rustc_codegen_gcc/Readme.md index ce5ee1e4adee6..9a7c624c9bc22 100644 --- a/compiler/rustc_codegen_gcc/Readme.md +++ b/compiler/rustc_codegen_gcc/Readme.md @@ -136,19 +136,21 @@ $ ./y.sh cargo build --manifest-path tests/hello-world/Cargo.toml ### Cargo ```bash -$ CHANNEL="release" $CG_GCCJIT_DIR/y.sh cargo run +$ CHANNEL=release $CG_GCCJIT_DIR/y.sh cargo run ``` -If you compiled cg_gccjit in debug mode (aka you didn't pass `--release` to `./y.sh test`) you should use `CHANNEL="debug"` instead or omit `CHANNEL="release"` completely. +If you compiled `cg_gcc` in debug mode (aka you didn't pass `--release` to `./y.sh build`) you should use `CHANNEL=debug` instead or omit `CHANNEL=release` completely. ### Rustc If you want to run `rustc` directly, you can do so with: ```bash -$ ./y.sh rustc my_crate.rs +$ CHANNEL=release ./y.sh rustc my_crate.rs ``` +If you compiled `cg_gcc` in debug mode (aka you didn't pass `--release` to `./y.sh build`) you should use `CHANNEL=debug` instead or omit `CHANNEL=release` completely. + You can do the same manually (although we don't recommend it): ```bash @@ -176,7 +178,7 @@ $ LIBRARY_PATH="[gcc-path value]" LD_LIBRARY_PATH="[gcc-path value]" rustc +$(ca More specific documentation is available in the [`doc`](./doc) folder: * [Common errors](./doc/errors.md) - * [Debugging GCC LTO](./doc/debugging-gcc-lto.md) + * [Debugging](./doc/debugging.md) * [Debugging libgccjit](./doc/debugging-libgccjit.md) * [Git subtree sync](./doc/subtree.md) * [List of useful commands](./doc/tips.md) diff --git a/compiler/rustc_codegen_gcc/build_system/Cargo.lock b/compiler/rustc_codegen_gcc/build_system/Cargo.lock index e727561a2bfba..5e761149eb3bc 100644 --- a/compiler/rustc_codegen_gcc/build_system/Cargo.lock +++ b/compiler/rustc_codegen_gcc/build_system/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "boml" diff --git a/compiler/rustc_codegen_gcc/build_system/asm-tester/Cargo.lock b/compiler/rustc_codegen_gcc/build_system/asm-tester/Cargo.lock new file mode 100644 index 0000000000000..9ad96acfda407 --- /dev/null +++ b/compiler/rustc_codegen_gcc/build_system/asm-tester/Cargo.lock @@ -0,0 +1,507 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "asm-tester" +version = "0.1.0" +dependencies = [ + "compiletest_rs", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "compiletest_rs" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f150fe9105fcd2a57cad53f0c079a24de65195903ef670990f5909f695eac04c" +dependencies = [ + "diff", + "filetime", + "getopts", + "lazy_static", + "libc", + "log", + "miow", + "regex", + "rustfix", + "serde", + "serde_derive", + "serde_json", + "tester", + "windows-sys 0.59.0", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "getopts" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libredox" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +dependencies = [ + "libc", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "miow" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "536bfad37a309d62069485248eeaba1e8d9853aaf951caaeaed0585a95346f08" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustfix" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fa69b198d894d84e23afde8e9ab2af4400b2cba20d6bf2b428a8b01c222c5a" +dependencies = [ + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "tester" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e8bf7e0eb2dd7b4228cc1b6821fc5114cd6841ae59f652a85488c016091e5f" +dependencies = [ + "cfg-if", + "getopts", + "libc", + "num_cpus", + "term", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/compiler/rustc_codegen_gcc/build_system/asm-tester/Cargo.toml b/compiler/rustc_codegen_gcc/build_system/asm-tester/Cargo.toml new file mode 100644 index 0000000000000..eeefe61bdc75b --- /dev/null +++ b/compiler/rustc_codegen_gcc/build_system/asm-tester/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "asm-tester" +version = "0.1.0" +edition = "2024" + +[dependencies] +compiletest_rs = "0.11.2" + +[[bin]] +name = "asm-tester" +path = "src/main.rs" + +[workspace] diff --git a/compiler/rustc_codegen_gcc/build_system/asm-tester/src/main.rs b/compiler/rustc_codegen_gcc/build_system/asm-tester/src/main.rs new file mode 100644 index 0000000000000..00ee4ac936520 --- /dev/null +++ b/compiler/rustc_codegen_gcc/build_system/asm-tester/src/main.rs @@ -0,0 +1,66 @@ +use std::path::PathBuf; + +#[derive(Default)] +struct Config { + llvm_filecheck: Option, + filters: Vec, + rustc_flags: Vec, +} + +impl Config { + fn new() -> Result { + // We skip the program's name. + let mut args = std::env::args().skip(1); + let mut config = Self::default(); + + while let Some(arg) = args.next() { + match arg.as_str() { + "--llvm-filecheck" => { + config.llvm_filecheck = args.next().map(PathBuf::from); + } + "--filter" => { + if let Some(arg) = args.next() { + config.filters.push(arg); + } + } + "--" => { + config.rustc_flags.extend(&mut args); + // Nothing else to be read but the `break` makes it more clear. + break; + } + arg => return Err(format!("Unknown argument {arg:?}")), + } + } + if config.llvm_filecheck.is_none() { + Err("Missing `--llvm-filecheck` option".to_owned()) + } else if config.rustc_flags.is_empty() { + Err("Missing rustc flags (passed after `--`)".to_owned()) + } else { + Ok(config) + } + } +} + +fn main() { + let Config { llvm_filecheck, filters, rustc_flags } = match Config::new() { + Ok(c) => c, + Err(error) => { + eprintln!("{error}"); + std::process::exit(1); + } + }; + + let mut test_config = compiletest_rs::Config::default(); + + test_config.mode = compiletest_rs::common::Mode::Assembly; + test_config.src_base = PathBuf::from("tests/asm"); + test_config.llvm_filecheck = llvm_filecheck; + test_config.filters = filters; + test_config.strict_headers = true; + test_config.build_base = PathBuf::from("build/tests/asm"); + test_config.target_rustcflags = Some(rustc_flags.join(" ")); + test_config.link_deps(); + test_config.clean_rmeta(); + + compiletest_rs::run_tests(&test_config) +} diff --git a/compiler/rustc_codegen_gcc/build_system/src/build.rs b/compiler/rustc_codegen_gcc/build_system/src/build.rs index 839c762fed742..bcd386bfebdae 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/build.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/build.rs @@ -132,6 +132,24 @@ pub fn build_sysroot(env: &HashMap, config: &ConfigInfo) -> Resu // Builds libs let mut rustflags = env.get("RUSTFLAGS").cloned().unwrap_or_default(); + + // Record the sysroot sources under the path the `rust-src` component uses, which is where + // rustc looks for them to turn a sysroot span into `/rustc/$hash`. Without this, ui tests + // print the build path where they expect `$SRC_DIR`. + let sysroot_source_dir = lib_path.join("rustlib/src/rust/library"); + rustflags.push_str(&format!( + " --remap-path-prefix={library_dir}={sysroot_source_dir}", + library_dir = std::path::absolute(&library_dir) + .map_err(|error| format!( + "Failed to get the absolute path of the sysroot sources: {error:?}" + ))? + .display(), + sysroot_source_dir = std::path::absolute(&sysroot_source_dir) + .map_err(|error| format!( + "Failed to get the absolute path of the sysroot sources: {error:?}" + ))? + .display(), + )); if config.sysroot_panic_abort { rustflags.push_str(" -Cpanic=abort -Zpanic-abort-tests"); } @@ -188,12 +206,33 @@ pub fn build_sysroot(env: &HashMap, config: &ConfigInfo) -> Resu // FIXME: should not use shell command! run_command(&[&"cp", &"-r", &dir_to_copy, &sysroot_path], None).map(|_| ()) }; - walk_dir( - library_dir.join(format!("target/{}/{}/deps", config.target_triple, channel)), - &mut copier.clone(), - &mut copier, - false, - )?; + let target_dir = library_dir.join(format!("target/{}/{}", config.target_triple, channel)); + let deps_dir = target_dir.join("deps"); + if deps_dir.is_dir() { + // Keep copying in the old directory just in case. + walk_dir(&deps_dir, &mut copier.clone(), &mut copier, false)?; + } else { + let build_dir = target_dir.join("build"); + walk_dir( + &build_dir, + &mut |package_dir: &Path| { + walk_dir( + package_dir, + &mut |unit_dir: &Path| { + let out_dir = unit_dir.join("out"); + if out_dir.is_dir() { + walk_dir(&out_dir, &mut copier.clone(), &mut copier.clone(), false)?; + } + Ok(()) + }, + &mut |_| Ok(()), + false, + ) + }, + &mut |_| Ok(()), + false, + )?; + } // Copy the source files to the sysroot (Rust for Linux needs this). let sysroot_src_path = start_dir.join("sysroot/lib/rustlib/src/rust"); @@ -227,7 +266,7 @@ fn build_codegen(args: &mut BuildArg) -> Result<(), String> { } run_command_with_output_and_env(&command, None, Some(&env))?; - args.config_info.setup(&mut env, false)?; + args.config_info.setup(&mut env, false, true)?; // We voluntarily ignore the error. let _ = fs::remove_dir_all("target/out"); diff --git a/compiler/rustc_codegen_gcc/build_system/src/clean.rs b/compiler/rustc_codegen_gcc/build_system/src/clean.rs index 43f01fdf35ecb..ec2092ee92ef5 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/clean.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/clean.rs @@ -74,7 +74,8 @@ fn clean_ui_tests() -> Result<(), String> { let path = Path::new(crate::BUILD_DIR) .join("rust/build/x86_64-unknown-linux-gnu/test/") .join(directory); - run_command(&[&"find", &path, &"-name", &"stamp", &"-delete"], None)?; + // The directory might not exist, so ignore the error. + let _ = run_command(&[&"find", &path, &"-name", &"stamp", &"-delete"], None); } Ok(()) } diff --git a/compiler/rustc_codegen_gcc/build_system/src/clippy.rs b/compiler/rustc_codegen_gcc/build_system/src/clippy.rs new file mode 100644 index 0000000000000..813d4b9141e1c --- /dev/null +++ b/compiler/rustc_codegen_gcc/build_system/src/clippy.rs @@ -0,0 +1,62 @@ +use std::path::Path; + +use crate::utils::{run_command_with_output, run_tool_and_install_it_if_not_present}; + +fn show_usage() { + println!( + r#" +`clippy` command help: + + --help : Show this help"# + ); +} + +pub fn run() -> Result<(), String> { + // We skip binary name and the `info` command. + let args = std::env::args().skip(2); + #[allow(clippy::never_loop)] + for arg in args { + match arg.as_str() { + "--help" => { + show_usage(); + return Ok(()); + } + _ => return Err(format!("Unknown option {arg}")), + } + } + + run_tool_and_install_it_if_not_present(&[ + &"cargo", + &"clippy", + &"--all-targets", + &"--", + &"-D", + &"warnings", + ])?; + run_command_with_output( + &[ + &"cargo", + &"clippy", + &"--all-targets", + &"--no-default-features", + &"--", + &"-D", + &"warnings", + ], + Some(Path::new(".")), + )?; + run_command_with_output( + &[ + &"cargo", + &"clippy", + &"--all-targets", + &"--manifest-path", + &"build_system/Cargo.toml", + &"--", + &"-D", + &"warnings", + ], + Some(Path::new(".")), + )?; + Ok(()) +} diff --git a/compiler/rustc_codegen_gcc/build_system/src/config.rs b/compiler/rustc_codegen_gcc/build_system/src/config.rs index 8eb6d8f019e1c..fd78f691d1657 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/config.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/config.rs @@ -314,6 +314,7 @@ impl ConfigInfo { &mut self, env: &mut HashMap, use_system_gcc: bool, + generate_out_dir: bool, ) -> Result<(), String> { env.insert("CARGO_INCREMENTAL".to_string(), "0".to_string()); @@ -444,12 +445,12 @@ impl ConfigInfo { self.rustc_command = vec![rustc]; self.rustc_command.extend_from_slice(&rustflags); - self.rustc_command.extend_from_slice(&[ - "-L".to_string(), - format!("crate={}", self.cargo_target_dir), - "--out-dir".to_string(), - self.cargo_target_dir.clone(), - ]); + self.rustc_command + .extend_from_slice(&["-L".to_string(), format!("crate={}", self.cargo_target_dir)]); + if generate_out_dir { + self.rustc_command + .extend_from_slice(&["--out-dir".to_string(), self.cargo_target_dir.clone()]); + } if !env.contains_key("RUSTC_LOG") { env.insert("RUSTC_LOG".to_string(), "warn".to_string()); diff --git a/compiler/rustc_codegen_gcc/build_system/src/fmt.rs b/compiler/rustc_codegen_gcc/build_system/src/fmt.rs index 91535f217e351..dc1ca1d3e82ae 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/fmt.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/fmt.rs @@ -1,7 +1,7 @@ use std::ffi::OsStr; use std::path::Path; -use crate::utils::{run_command_with_output, walk_dir}; +use crate::utils::{run_command_with_output, run_tool_and_install_it_if_not_present, walk_dir}; fn show_usage() { println!( @@ -31,8 +31,9 @@ pub fn run() -> Result<(), String> { let cmd: &[&dyn AsRef] = if check { &[&"cargo", &"fmt", &"--check"] } else { &[&"cargo", &"fmt"] }; - run_command_with_output(cmd, Some(Path::new(".")))?; + run_tool_and_install_it_if_not_present(cmd)?; run_command_with_output(cmd, Some(Path::new("build_system")))?; + run_command_with_output(cmd, Some(Path::new("build_system/asm-tester")))?; run_rustfmt_recursively("tests/run", check) } diff --git a/compiler/rustc_codegen_gcc/build_system/src/main.rs b/compiler/rustc_codegen_gcc/build_system/src/main.rs index ae975c94fff25..37b1f306817fd 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/main.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/main.rs @@ -3,6 +3,7 @@ use std::{env, process}; mod abi_test; mod build; mod clean; +mod clippy; mod clone_gcc; mod config; mod fmt; @@ -12,6 +13,7 @@ mod prepare; mod rust_tools; mod rustc_info; mod test; +mod todo; mod utils; const BUILD_DIR: &str = "build"; @@ -24,43 +26,67 @@ macro_rules! arg_error { }}; } -fn usage() { - println!( - "\ +macro_rules! commands_decl { + ($($variant:ident: $doc_name:literal => $doc:literal ,)+) => { + enum Command { + $($variant),+ + } + + impl<'a> From> for Command { + fn from(arg: Option<&'a str>) -> Self { + match arg { + $(Some($doc_name) => Self::$variant,)+ + Some("--help") => { + usage(); + process::exit(0); + } + Some(flag) if flag.starts_with('-') => arg_error!("Expected command found flag {}", flag), + Some(command) => arg_error!("Unknown command {}", command), + None => { + usage(); + process::exit(0); + } + } + } + } + + fn usage() { + println!("\ rustc_codegen_gcc build system Usage: build_system [command] [options] Options: - --help : Displays this help message. + --help : Displays this help message. + +Commands:", + ); + let mut commands = vec![$(($doc_name, $doc),)+]; + let longest = commands.iter().map(|(name, _)| name.len()).max().unwrap(); -Commands: - cargo : Executes a cargo command. - rustc : Compiles the program using the GCC compiler. - clean : Cleans the build directory, removing all compiled files and artifacts. - prepare : Prepares the environment for building, including fetching dependencies and setting up configurations. - build : Compiles the project. - test : Runs tests for the project. - info : Displays information about the build environment and project configuration. - clone-gcc : Clones the GCC compiler from a specified source. - fmt : Runs rustfmt - fuzz : Fuzzes `cg_gcc` using rustlantis - abi-test : Runs the abi-cafe test suite on the codegen, checking for ABI compatibility with LLVM" - ); + commands.sort_unstable_by(|a, b| a.0.cmp(b.0)); + for (name, doc) in commands { + let spacing = std::iter::repeat(' ').take(longest - name.len() + 1).collect::(); + eprintln!(" {name}{spacing}: {doc}."); + } + } + } } -pub enum Command { - Cargo, - Clean, - CloneGcc, - Prepare, - Build, - Rustc, - Test, - Info, - Fmt, - Fuzz, - AbiTest, +commands_decl! { + Cargo: "cargo" => "Executes a cargo command", + Clean: "clean" => "Cleans the build directory, removing all compiled files and artifacts", + Clippy: "clippy" => "Runs clippy", + CloneGcc: "clone-gcc" => "Clones the GCC compiler from a specified source", + Prepare: "prepare" => "Prepares the environment for building, including fetching dependencies and setting up configurations", + Build: "build" => "Compiles the project", + Rustc: "rustc" => "Compiles the program using the GCC compiler", + Test: "test" => "Runs tests for the project", + Info: "info" => "Displays information about the build environment and project configuration", + Fmt: "fmt" => "Runs rustfmt", + Fuzz: "fuzz" => "Fuzzes `cg_gcc` using `rustlantis`", + AbiTest: "abi-test" => "Runs the abi-cafe test suite on the codegen, checking for ABI compatibility with LLVM", + CheckTodo: "check-todo" => "Checks todo in the project", } fn main() { @@ -70,31 +96,7 @@ fn main() { } } - let command = match env::args().nth(1).as_deref() { - Some("cargo") => Command::Cargo, - Some("rustc") => Command::Rustc, - Some("clean") => Command::Clean, - Some("prepare") => Command::Prepare, - Some("build") => Command::Build, - Some("test") => Command::Test, - Some("info") => Command::Info, - Some("clone-gcc") => Command::CloneGcc, - Some("abi-test") => Command::AbiTest, - Some("fmt") => Command::Fmt, - Some("fuzz") => Command::Fuzz, - Some("--help") => { - usage(); - process::exit(0); - } - Some(flag) if flag.starts_with('-') => arg_error!("Expected command found flag {}", flag), - Some(command) => arg_error!("Unknown command {}", command), - None => { - usage(); - process::exit(0); - } - }; - - if let Err(e) = match command { + if let Err(e) = match Command::from(env::args().nth(1).as_deref()) { Command::Cargo => rust_tools::run_cargo(), Command::Rustc => rust_tools::run_rustc(), Command::Clean => clean::run(), @@ -106,8 +108,13 @@ fn main() { Command::Fmt => fmt::run(), Command::Fuzz => fuzz::run(), Command::AbiTest => abi_test::run(), + Command::Clippy => clippy::run(), + Command::CheckTodo => todo::run(), } { eprintln!("Command failed to run: {e}"); - process::exit(1); + // CI needs to tell a build system error apart from the test failures some suites expect. + let exit_code = + if e == test::TESTS_FAILED_ERROR { test::TESTS_FAILED_EXIT_CODE } else { 1 }; + process::exit(exit_code); } } diff --git a/compiler/rustc_codegen_gcc/build_system/src/rust_tools.rs b/compiler/rustc_codegen_gcc/build_system/src/rust_tools.rs index b1faa27acc4a2..1b50f11c3d324 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/rust_tools.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/rust_tools.rs @@ -72,7 +72,7 @@ impl RustcTools { let mut env: HashMap = std::env::vars().collect(); let mut config = ConfigInfo::default(); - config.setup(&mut env, false)?; + config.setup(&mut env, false, false)?; let toolchain = get_toolchain()?; let toolchain_version = rustc_toolchain_version_info(&toolchain)?; diff --git a/compiler/rustc_codegen_gcc/build_system/src/test.rs b/compiler/rustc_codegen_gcc/build_system/src/test.rs index 2475a3a6a7155..6f33bdc392984 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/test.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/test.rs @@ -1,18 +1,29 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::ffi::OsStr; -use std::fs::{File, remove_dir_all}; +use std::fs::{File, read_to_string, remove_dir_all}; use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; use std::str::FromStr; +use boml::Toml; + use crate::build; use crate::config::{Channel, ConfigInfo}; use crate::utils::{ create_dir, get_sysroot_dir, get_toolchain, git_clone, git_clone_root_dir, remove_file, - run_command, run_command_with_env, run_command_with_output_and_env, rustc_version_info, - split_args, walk_dir, + run_command, run_command_with_env, run_command_with_output_and_env, + run_command_with_output_and_env_no_err, rustc_version_info, split_args, walk_dir, }; +/// Exit code of `y.sh test` when the tests ran and reported failures, as opposed to the build +/// system failing to run them at all. CI relies on the distinction: the suites of known-failing +/// tests are expected to report failures, but a broken build system must never pass silently. +pub const TESTS_FAILED_EXIT_CODE: i32 = 2; + +/// The error returned for that case. `main` compares against it to pick the exit code, so no other +/// error may use this message. +pub const TESTS_FAILED_ERROR: &str = "the test suite reported failures"; + type Env = HashMap; type Runner = fn(&Env, &TestArg) -> Result<(), String>; type Runners = HashMap<&'static str, (&'static str, Runner)>; @@ -28,8 +39,12 @@ fn get_runners() -> Runners { ("Run failing ui pattern tests", test_failing_ui_pattern_tests), ); runners.insert("--test-failing-rustc", ("Run failing rustc tests", test_failing_rustc)); + runners.insert("--run-ui-tests", ("Run specified rustc UI tests", run_ui_tests)); runners.insert("--projects", ("Run the tests of popular crates", test_projects)); runners.insert("--test-libcore", ("Run libcore tests", test_libcore)); + runners.insert("--test-release-libcore", ("Run libcore tests", test_release_libcore)); + runners.insert("--test-libcore-doctests", ("Run libcore doc-tests", test_libcore_doctests)); + runners.insert("--alloc-tests", ("Run alloc tests", test_alloc)); runners.insert("--clean", ("Empty cargo target directory", clean)); runners.insert("--build-sysroot", ("Build sysroot", build_sysroot)); runners.insert("--std-tests", ("Run std tests", std_tests)); @@ -42,8 +57,10 @@ fn get_runners() -> Runners { ); runners.insert("--extended-regex-tests", ("Run extended regex tests", extended_regex_tests)); runners.insert("--mini-tests", ("Run mini tests", mini_tests)); + runners.insert("--gcc-asm-tests", ("Run cg_gcc asm tests", test_asm)); runners.insert("--cargo-tests", ("Run cargo tests", cargo_tests)); runners.insert("--no-builtins-tests", ("Test #![no_builtins] attribute", no_builtins_tests)); + runners.insert("--stdarch-tests", ("Run stdarch tests", test_stdarch as Runner)); runners } @@ -505,6 +522,26 @@ fn std_tests(env: &Env, args: &TestArg) -> Result<(), String> { Ok(()) } +fn get_llvm_filecheck(env: &Env) -> Result { + match run_command_with_env( + &[ + &"bash", + &"-c", + &"which FileCheck-10 || \ + which FileCheck-11 || \ + which FileCheck-12 || \ + which FileCheck-13 || \ + which FileCheck-14 || \ + which FileCheck", + ], + None, + Some(env), + ) { + Ok(cmd) => Ok(String::from_utf8_lossy(&cmd.stdout).trim().to_string()), + Err(_) => Err("Failed to retrieve LLVM FileCheck, ignoring...".to_owned()), + } +} + fn setup_rustc(env: &mut Env, args: &TestArg) -> Result { let toolchain = format!( "+{channel}-{host}", @@ -548,23 +585,10 @@ fn setup_rustc(env: &mut Env, args: &TestArg) -> Result { let rustc = rustc.trim().to_owned(); if rustc.is_empty() { Err("`rustc` path is empty".to_string()) } else { Ok(rustc) } })?; - let llvm_filecheck = match run_command_with_env( - &[ - &"bash", - &"-c", - &"which FileCheck-10 || \ - which FileCheck-11 || \ - which FileCheck-12 || \ - which FileCheck-13 || \ - which FileCheck-14 || \ - which FileCheck", - ], - rust_dir, - Some(env), - ) { - Ok(cmd) => String::from_utf8_lossy(&cmd.stdout).to_string(), - Err(_) => { - eprintln!("Failed to retrieve LLVM FileCheck, ignoring..."); + let llvm_filecheck = match get_llvm_filecheck(env) { + Ok(l) => l, + Err(error) => { + eprintln!("{error}"); // FIXME: the test tests/run-make/no-builtins-attribute will fail if we cannot find // FileCheck. String::new() @@ -634,7 +658,7 @@ fn asm_tests(env: &Env, args: &TestArg) -> Result<(), String> { &"0", &"--set", &"build.compiletest-allow-stage0=true", - &"tests/assembly-llvm/asm", + &"tests/assembly-gcc/asm", &"--compiletest-rustc-args", &rustc_args, ], @@ -755,10 +779,117 @@ fn test_projects(env: &Env, args: &TestArg) -> Result<(), String> { } fn test_libcore(env: &Env, args: &TestArg) -> Result<(), String> { + test_libcore_inner(env, args, false) +} + +fn test_release_libcore(env: &Env, args: &TestArg) -> Result<(), String> { + test_libcore_inner(env, args, true) +} + +fn test_libcore_inner(env: &Env, args: &TestArg, release: bool) -> Result<(), String> { // FIXME: create a function "display_if_not_quiet" or something along the line. println!("[TEST] libcore"); let path = get_sysroot_dir().join("sysroot_src/library/coretests"); let _ = remove_dir_all(path.join("target")); + let mut command: Vec<&dyn AsRef> = vec![&"test"]; + if release { + command.push(&"--release"); + } + run_cargo_command(&command, Some(&path), env, args)?; + Ok(()) +} + +/// Returns the edition declared in the manifest of the given library crate, so that the doctests +/// are run with the same edition as the crate they are extracted from. +fn get_crate_edition(crate_dir: &Path) -> Result { + let manifest_path = crate_dir.join("Cargo.toml"); + let content = read_to_string(&manifest_path) + .map_err(|error| format!("Failed to read `{}`: {error:?}", manifest_path.display()))?; + let manifest = Toml::parse(&content) + .map_err(|error| format!("Failed to parse `{}`: {error:?}", manifest_path.display()))?; + manifest + .get_table("package") + .and_then(|package| package.get_string("edition")) + .map(|edition| edition.to_string()) + .map_err(|error| { + format!("Failed to get `package.edition` from `{}`: {error:?}", manifest_path.display()) + }) +} + +fn test_libcore_doctests(env: &Env, args: &TestArg) -> Result<(), String> { + // FIXME: create a function "display_if_not_quiet" or something along the line. + println!("[TEST] libcore doctests"); + + let library_dir = get_sysroot_dir().join("sysroot_src/library"); + let edition = get_crate_edition(&library_dir.join("core"))?; + // `rustdoc` is called directly instead of through `cargo test --doc` because `cargo` builds its + // own `core` and passes it with `--extern`, which then conflicts with the `core` of the sysroot + // the doctests are linked against ("duplicate lang item" errors). + let toolchain = get_toolchain()?; + let toolchain_arg = format!("+{toolchain}"); + let rustflags = split_args(&env.get("RUSTFLAGS").cloned().unwrap_or_default())?; + // `-Zunstable-options` is needed for `--test-args`. + let mut command: Vec<&dyn AsRef> = vec![ + &"rustdoc", + &toolchain_arg, + &"--test", + &"core/src/lib.rs", + &"--crate-name", + &"core", + &"--crate-type", + &"lib", + &"--edition", + &edition, + &"-Zunstable-options", + // FIXME: remove `-Zforce-unstable-if-unmarked` once the doctest of + // `core::io::ErrorKind`'s `Display` impl declares `#![feature(core_io)]` upstream: without + // it, that doctest fails to compile with `E0658` on any backend. + &"-Zforce-unstable-if-unmarked", + // FIXME: one test cannot compile due to an upstream bug in the new trait solver. + &"-Znext-solver=coherence", + ]; + for flag in &rustflags { + command.push(flag); + } + // Additional arguments are forwarded to the test harness, so that a subset of the doctests can + // be run. + let test_args = + args.test_args.iter().map(|test_arg| format!("--test-args={test_arg}")).collect::>(); + for test_arg in &test_args { + command.push(test_arg); + } + run_command_with_output_and_env(&command, Some(&library_dir), Some(env))?; + Ok(()) +} + +fn test_stdarch(env: &Env, args: &TestArg) -> Result<(), String> { + println!("[TEST] stdarch"); + let manifest_path = get_sysroot_dir().join("sysroot_src/library/stdarch/Cargo.toml"); + let mut env = env.clone(); + + // `config.setup` already baked `CG_RUSTFLAGS` into `RUSTFLAGS`, so append the lint-allow to + // `RUSTFLAGS` directly (which `run_cargo_command` also propagates to `RUSTDOCFLAGS`). + let rustflags = env.get("RUSTFLAGS").cloned().unwrap_or_default(); + env.insert( + "RUSTFLAGS".to_string(), + format!("{rustflags} -Ainternal_features").trim().to_owned(), + ); + env.insert("TARGET".to_string(), args.config_info.target_triple.clone()); + + let mut command: Vec<&dyn AsRef> = + vec![&"test", &"--manifest-path", &manifest_path, &"--"]; + for test_name in &args.test_args { + command.push(test_name); + } + run_cargo_command(&command, None, &env, args)?; + Ok(()) +} + +fn test_alloc(env: &Env, args: &TestArg) -> Result<(), String> { + // FIXME: create a function "display_if_not_quiet" or something along the line. + println!("[TEST] alloc"); + let path = get_sysroot_dir().join("sysroot_src/library/alloctests"); + let _ = remove_dir_all(path.join("target")); // FIXME(antoyo): run in release mode when we fix the failures. run_cargo_command(&[&"test"], Some(&path), env, args)?; Ok(()) @@ -908,7 +1039,6 @@ fn contains_ui_error_patterns(file_path: &Path, keep_lto_tests: bool) -> Result< "//@ known-bug", "-Cllvm-args", "//~", - "thread", ] .iter() .any(|check| line.contains(check)) @@ -934,10 +1064,7 @@ fn contains_ui_error_patterns(file_path: &Path, keep_lto_tests: bool) -> Result< eprintln!("nothing found for {file_path:?}"); } // The files in this directory contain errors. - if file_path.contains("/error-emitter/") { - return Ok(true); - } - Ok(false) + Ok(file_path.contains("/error-emitter/")) } // # Parameters @@ -947,6 +1074,8 @@ fn contains_ui_error_patterns(file_path: &Path, keep_lto_tests: bool) -> Result< // * `prepare_files_callback`: A callback function that prepares the files needed for the test. Its used to remove/retain tests giving Error to run various rust test suits. // * `run_error_pattern_test`: A boolean that determines whether to run only error pattern tests. // * `test_type`: A string that indicates the type of the test being run. +// * `retained_tests_list_path`: The list of tests that `prepare_files_callback` retained, if any. +// It is checked against the tests remaining after the filtering to report dead lines. // fn test_rustc_inner( env: &Env, @@ -954,7 +1083,7 @@ fn test_rustc_inner( prepare_files_callback: F, run_error_pattern_test: bool, test_type: &str, - run_ignored_tests: bool, + retained_tests_list_path: Option<&str>, ) -> Result<(), String> where F: Fn(&Path) -> Result, @@ -970,74 +1099,57 @@ where } if test_type == "ui" { - if run_error_pattern_test { - // After we removed the error tests that are known to panic with rustc_codegen_gcc, we now remove the passing tests since this runs the error tests. - walk_dir( - rust_path.join("tests/ui"), - &mut |_dir| Ok(()), - &mut |file_path| { - if contains_ui_error_patterns(file_path, args.keep_lto_tests)? { - Ok(()) - } else { - remove_file(file_path).map_err(|e| e.to_string()) - } - }, - true, - )?; - } else { - walk_dir( - rust_path.join("tests/ui"), - &mut |dir| { - let dir_name = dir.file_name().and_then(|name| name.to_str()).unwrap_or(""); - if ["abi", "extern", "proc-macro", "threads-sendsync"].contains(&dir_name) { - remove_dir_all(dir).map_err(|error| { - format!("Failed to remove folder `{}`: {:?}", dir.display(), error) - })?; - } - Ok(()) - }, - &mut |_| Ok(()), - false, - )?; - - // These two functions are used to remove files that are known to not be working currently - // with the GCC backend to reduce noise. - fn dir_handling(keep_lto_tests: bool) -> impl Fn(&Path) -> Result<(), String> { - move |dir| { - if dir.file_name().map(|name| name == "auxiliary").unwrap_or(true) { - return Ok(()); - } - - walk_dir( - dir, - &mut dir_handling(keep_lto_tests), - &mut file_handling(keep_lto_tests), - false, - ) + // Each mode runs one half of the ui tests and removes the other: `run_error_pattern_test` + // runs the tests expected to error, the other mode runs the rest. Only `.rs` files outside + // `auxiliary` are tests, so the expected output and the auxiliary crates are left alone. + fn dir_handling( + keep_lto_tests: bool, + remove_error_pattern_tests: bool, + ) -> impl Fn(&Path) -> Result<(), String> { + move |dir| { + if dir.file_name().map(|name| name == "auxiliary").unwrap_or(true) { + return Ok(()); } + + walk_dir( + dir, + &mut dir_handling(keep_lto_tests, remove_error_pattern_tests), + &mut file_handling(keep_lto_tests, remove_error_pattern_tests), + false, + ) } + } - fn file_handling(keep_lto_tests: bool) -> impl Fn(&Path) -> Result<(), String> { - move |file_path| { - if !file_path.extension().map(|extension| extension == "rs").unwrap_or(false) { - return Ok(()); - } - let path_str = file_path.display().to_string().replace("\\", "/"); - if valid_ui_error_pattern_test(&path_str) { - return Ok(()); - } else if contains_ui_error_patterns(file_path, keep_lto_tests)? { - return remove_file(&file_path); - } - Ok(()) + fn file_handling( + keep_lto_tests: bool, + remove_error_pattern_tests: bool, + ) -> impl Fn(&Path) -> Result<(), String> { + move |file_path| { + if !file_path.extension().map(|extension| extension == "rs").unwrap_or(false) { + return Ok(()); + } + let path_str = file_path.display().to_string().replace("\\", "/"); + if valid_ui_error_pattern_test(&path_str) { + return Ok(()); + } + if contains_ui_error_patterns(file_path, keep_lto_tests)? + == remove_error_pattern_tests + { + return remove_file(file_path); } + Ok(()) } + } - walk_dir( - rust_path.join("tests/ui"), - &mut dir_handling(args.keep_lto_tests), - &mut file_handling(args.keep_lto_tests), - false, - )?; + let remove_error_pattern_tests = !run_error_pattern_test; + walk_dir( + rust_path.join("tests/ui"), + &mut dir_handling(args.keep_lto_tests, remove_error_pattern_tests), + &mut file_handling(args.keep_lto_tests, remove_error_pattern_tests), + false, + )?; + if let Some(retained_tests_list_path) = retained_tests_list_path { + check_for_dead_listed_tests(&rust_path, retained_tests_list_path)?; } let nb_parts = args.nb_parts.unwrap_or(0); if nb_parts > 0 { @@ -1097,7 +1209,7 @@ where env.get_mut("RUSTFLAGS").unwrap().clear(); let test_dir = format!("tests/{test_type}"); - let mut command: Vec<&dyn AsRef> = vec![ + let command: Vec<&dyn AsRef> = vec![ &"./x.py", &"test", &"--run", @@ -1112,19 +1224,85 @@ where &"--bypass-ignore-backends", ]; - if run_ignored_tests { - command.push(&"--"); - command.push(&"--ignored"); + run_test_command(&command, &rust_path, &env) +} + +/// Reads the list of tests at `list_path`, checking that each of them still exists in the rust +/// checkout at `rust_path` and that none is listed twice. +/// +/// Both problems make a line a no-op: the test it names is neither kept nor removed, so the test +/// suite silently drifts away from what the list claims to describe. +fn read_test_list(rust_path: &Path, list_path: &str) -> Result, String> { + let content = std::fs::read_to_string(list_path) + .map_err(|error| format!("Failed to read `{list_path}`: {error:?}"))?; + + let mut tests = Vec::new(); + let mut seen = HashSet::new(); + let mut missing = Vec::new(); + let mut duplicated = Vec::new(); + + for line in content.lines().map(|line| line.trim()).filter(|line| !line.is_empty()) { + if !seen.insert(line) { + duplicated.push(line); + continue; + } + if !rust_path.join(line.trim_end_matches('/')).exists() { + missing.push(line); + } + tests.push(line.to_string()); } - run_command_with_output_and_env(&command, Some(&rust_path), Some(&env))?; - Ok(()) + if missing.is_empty() && duplicated.is_empty() { + return Ok(tests); + } + + let mut error = format!("`{list_path}` is out of date:\n"); + if !missing.is_empty() { + error.push_str(&format!( + "\nThese tests no longer exist in `{rust_path}`:\n{missing}\n", + rust_path = rust_path.display(), + missing = missing.join("\n"), + )); + } + if !duplicated.is_empty() { + error.push_str(&format!( + "\nThese tests are listed more than once:\n{}\n", + duplicated.join("\n") + )); + } + error.push_str( + "\nEvery line must name a test that exists, exactly once, otherwise the line filters \ + nothing. Delete the stale lines, or update them to the test's current path.", + ); + Err(error) +} + +/// Checks that every test listed in `list_path` survived the filtering done by +/// `contains_ui_error_patterns`. +fn check_for_dead_listed_tests(rust_path: &Path, list_path: &str) -> Result<(), String> { + let listed_tests = std::fs::read_to_string(list_path) + .map_err(|error| format!("Failed to read `{list_path}`: {error:?}"))?; + let dead_tests = listed_tests + .lines() + .map(|line| line.trim()) + .filter(|line| !line.is_empty() && !rust_path.join(line).exists()) + .collect::>(); + if dead_tests.is_empty() { + return Ok(()); + } + Err(format!( + "The following tests listed in `{list_path}` are filtered out before the tests are run, \ + so listing them has no effect:\n{}\n\nThis happens when a test contains an error pattern \ + (like `//~` or `//@ known-bug`), in which case it should be removed from `{list_path}`, \ + or when it uses LTO, in which case it should be moved to `tests/failing-lto-tests.txt`.", + dead_tests.join("\n") + )) } fn test_rustc(env: &Env, args: &TestArg) -> Result<(), String> { - test_rustc_inner(env, args, |_| Ok(false), false, "run-make", false)?; - test_rustc_inner(env, args, |_| Ok(false), false, "run-make-cargo", false)?; - test_rustc_inner(env, args, |_| Ok(false), false, "ui", false) + test_rustc_inner(env, args, |_| Ok(false), false, "run-make", None)?; + test_rustc_inner(env, args, |_| Ok(false), false, "run-make-cargo", None)?; + test_rustc_inner(env, args, |_| Ok(false), false, "ui", None) } fn test_failing_rustc(env: &Env, args: &TestArg) -> Result<(), String> { @@ -1134,7 +1312,7 @@ fn test_failing_rustc(env: &Env, args: &TestArg) -> Result<(), String> { retain_files_callback("tests/failing-run-make-tests.txt", "run-make"), false, "run-make", - true, + None, ); let run_make_cargo_result = test_rustc_inner( @@ -1142,8 +1320,8 @@ fn test_failing_rustc(env: &Env, args: &TestArg) -> Result<(), String> { args, retain_files_callback("tests/failing-run-make-tests.txt", "run-make-cargo"), false, - "run-make", - true, + "run-make-cargo", + None, ); let ui_result = test_rustc_inner( @@ -1152,36 +1330,51 @@ fn test_failing_rustc(env: &Env, args: &TestArg) -> Result<(), String> { retain_files_callback("tests/failing-ui-tests.txt", "ui"), false, "ui", - true, + Some("tests/failing-ui-tests.txt"), ); - run_make_result.and(run_make_cargo_result).and(ui_result) + combine_test_results([run_make_result, run_make_cargo_result, ui_result]) +} + +/// Combines the results of several test suites, letting a build system error win over a test +/// failure so that a broken build system is never reported to CI as the failures those suites +/// expect. +fn combine_test_results(results: [Result<(), String>; N]) -> Result<(), String> { + let mut tests_failed = false; + for result in results { + match result { + Ok(()) => {} + Err(error) if error == TESTS_FAILED_ERROR => tests_failed = true, + Err(error) => return Err(error), + } + } + if tests_failed { Err(TESTS_FAILED_ERROR.to_string()) } else { Ok(()) } } fn test_successful_rustc(env: &Env, args: &TestArg) -> Result<(), String> { test_rustc_inner( env, args, - remove_files_callback("tests/failing-ui-tests.txt", "ui"), + remove_files_callback("tests/failing-ui-tests.txt"), false, "ui", - false, + None, )?; test_rustc_inner( env, args, - remove_files_callback("tests/failing-run-make-tests.txt", "run-make"), + remove_files_callback("tests/failing-run-make-tests.txt"), false, "run-make", - false, + None, )?; test_rustc_inner( env, args, - remove_files_callback("tests/failing-run-make-tests.txt", "run-make-cargo"), + remove_files_callback("tests/failing-run-make-tests.txt"), false, "run-make-cargo", - false, + None, ) } @@ -1189,20 +1382,73 @@ fn test_failing_ui_pattern_tests(env: &Env, args: &TestArg) -> Result<(), String test_rustc_inner( env, args, - remove_files_callback("tests/failing-ice-tests.txt", "ui"), + remove_files_callback("tests/failing-ice-tests.txt"), true, "ui", - false, + None, ) } +fn run_ui_tests(env: &Env, args: &TestArg) -> Result<(), String> { + let mut env = env.clone(); + let rust_path = setup_rustc(&mut env, args)?; + + let extra = + if args.is_using_gcc_master_branch() { "" } else { " -Csymbol-mangling-version=v0" }; + + let rustc_args = format!( + "{test_flags} -Zcodegen-backend={backend} --sysroot {sysroot}{extra}", + test_flags = env.get("TEST_FLAGS").unwrap_or(&String::new()), + backend = args.config_info.cg_backend_path, + sysroot = args.config_info.sysroot_path, + extra = extra, + ); + + env.get_mut("RUSTFLAGS").unwrap().clear(); + + let mut command: Vec<&dyn AsRef> = vec![ + &"./x.py", + &"test", + &"--run", + &"always", + &"--stage", + &"0", + &"--set", + &"build.compiletest-allow-stage0=true", + &"--compiletest-rustc-args", + &rustc_args, + &"--bypass-ignore-backends", + &"--force-rerun", + ]; + + for test_name in &args.test_args { + command.push(test_name); + } + + run_test_command(&command, &rust_path, &env) +} + +/// Runs the command that actually runs a test suite, mapping its failure to `TESTS_FAILED_ERROR`. +fn run_test_command( + command: &[&dyn AsRef], + rust_path: &Path, + env: &Env, +) -> Result<(), String> { + if let Err(error) = run_command_with_output_and_env(command, Some(rust_path), Some(env)) { + // The failures themselves were already streamed to the console. + eprintln!("{error}"); + return Err(TESTS_FAILED_ERROR.to_string()); + } + Ok(()) +} + fn retain_files_callback<'a>( file_path: &'a str, test_type: &'a str, ) -> impl Fn(&Path) -> Result + 'a { move |rust_path| { - let files = std::fs::read_to_string(file_path).unwrap_or_default(); - let first_file_name = files.lines().next().unwrap_or(""); + let tests = read_test_list(rust_path, file_path)?; + let first_file_name = tests.first().map(String::as_str).unwrap_or(""); // If the first line ends with a `/`, we treat all lines in the file as a directory. if first_file_name.ends_with('/') { // Treat as directory @@ -1244,59 +1490,91 @@ fn retain_files_callback<'a>( } // Putting back only the failing ones. - if let Ok(files) = std::fs::read_to_string(file_path) { - for file in files.split('\n').map(|line| line.trim()).filter(|line| !line.is_empty()) { - run_command(&[&"git", &"checkout", &"--", &file], Some(rust_path))?; - } - } else { - println!("Failed to read `{file_path}`, not putting back failing {test_type} tests"); + for test in &tests { + run_command(&[&"git", &"checkout", &"--", test], Some(rust_path))?; } Ok(true) } } -fn remove_files_callback<'a>( - file_path: &'a str, - test_type: &'a str, -) -> impl Fn(&Path) -> Result + 'a { +fn remove_files_callback(file_path: &str) -> impl Fn(&Path) -> Result + '_ { move |rust_path| { - let files = std::fs::read_to_string(file_path).unwrap_or_default(); - let first_file_name = files.lines().next().unwrap_or(""); + let tests = read_test_list(rust_path, file_path)?; + let first_file_name = tests.first().map(String::as_str).unwrap_or(""); // If the first line ends with a `/`, we treat all lines in the file as a directory. if first_file_name.ends_with('/') { // Removing the failing tests. - if let Ok(files) = std::fs::read_to_string(file_path) { - for file in - files.split('\n').map(|line| line.trim()).filter(|line| !line.is_empty()) - { - let path = rust_path.join(file); - if let Err(e) = remove_dir_all(&path) { - println!("Failed to remove directory `{}`: {}", path.display(), e); - } - } - } else { - println!( - "Failed to read `{file_path}`, not putting back failing {test_type} tests" - ); + for test in &tests { + let path = rust_path.join(test); + remove_dir_all(&path).map_err(|error| { + format!("Failed to remove directory `{}`: {error}", path.display()) + })?; } } else { // Removing the failing tests. - if let Ok(files) = std::fs::read_to_string(file_path) { - for file in - files.split('\n').map(|line| line.trim()).filter(|line| !line.is_empty()) - { - let path = rust_path.join(file); - remove_file(&path)?; - } - } else { - println!("Failed to read `{file_path}`, not putting back failing ui tests"); + for test in &tests { + remove_file(&rust_path.join(test))?; } } Ok(true) } } +fn test_asm(env: &Env, args: &TestArg) -> Result<(), String> { + fn is_path_time_more_recent(ref_time: std::time::SystemTime, path: &str) -> bool { + std::fs::metadata(path) + .and_then(|metadata| metadata.modified()) + .is_ok_and(|time| ref_time < time) + } + + // FIXME: create a function "display_if_not_quiet" or something along the line. + println!("[TEST] cg_gcc assembly"); + let llvm_filecheck = get_llvm_filecheck(env)?; + + let target_dir = std::env::current_dir().unwrap().join("build_system/asm-tester/target"); + + // All this code is because `cargo` keeps recompiling this file, and we can't figure out why. + let binary_file_path = "build_system/asm-tester/target/debug/asm-tester"; + let mut need_recompilation = true; + if let Ok(metadata) = std::fs::metadata(binary_file_path) + && let Ok(ref_time) = metadata.modified() + && !is_path_time_more_recent(ref_time, "build_system/asm-tester/Cargo.toml") + && !is_path_time_more_recent(ref_time, "build_system/asm-tester/Cargo.lock") + && !is_path_time_more_recent(ref_time, "build_system/asm-tester/src/main.rs") + { + need_recompilation = false; + } + + if need_recompilation { + let build_asm_args: Vec<&dyn AsRef> = vec![ + &"cargo", + &"build", + &"--manifest-path", + &"build_system/asm-tester/Cargo.toml", + &"--target-dir", + &target_dir, + &"--", + ]; + run_command_with_output_and_env_no_err(&build_asm_args, Some(Path::new(".")), Some(env))?; + } + + let mut test_asm_args: Vec<&dyn AsRef> = vec![ + &"build_system/asm-tester/target/debug/asm-tester", + &"--llvm-filecheck", + &llvm_filecheck, + ]; + for test_arg in &args.test_args { + test_asm_args.push(&"--filter"); + test_asm_args.push(test_arg); + } + test_asm_args.push(&"--"); + for arg in args.config_info.rustc_command_vec().into_iter().skip(1) { + test_asm_args.push(arg); + } + run_command_with_output_and_env_no_err(&test_asm_args, Some(Path::new(".")), Some(env)) +} + fn run_all(env: &Env, args: &TestArg) -> Result<(), String> { clean(env, args)?; mini_tests(env, args)?; @@ -1308,6 +1586,7 @@ fn run_all(env: &Env, args: &TestArg) -> Result<(), String> { cargo_tests(env, args)?; no_builtins_tests(env, args)?; test_rustc(env, args)?; + test_asm(env, args)?; Ok(()) } @@ -1329,7 +1608,7 @@ pub fn run() -> Result<(), String> { return Ok(()); } - args.config_info.setup(&mut env, args.use_system_gcc)?; + args.config_info.setup(&mut env, args.use_system_gcc, true)?; if args.runners.is_empty() { run_all(&env, &args)?; @@ -1342,3 +1621,58 @@ pub fn run() -> Result<(), String> { Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + fn write_test_list(directory: &Path, content: &str) -> PathBuf { + let list_path = directory.join("failing-tests.txt"); + std::fs::write(&list_path, content).unwrap(); + list_path + } + + #[test] + fn test_combine_test_results() { + let tests_failed = || Err(TESTS_FAILED_ERROR.to_string()); + let build_error = || Err("could not clone rust".to_string()); + + assert_eq!(combine_test_results([Ok(()), Ok(())]), Ok(())); + assert_eq!(combine_test_results([Ok(()), tests_failed()]), tests_failed()); + assert_eq!(combine_test_results([Ok(()), build_error()]), build_error()); + // A build system error wins, whichever suite reported it. + assert_eq!(combine_test_results([tests_failed(), build_error()]), build_error()); + assert_eq!(combine_test_results([build_error(), tests_failed()]), build_error()); + } + + #[test] + fn test_read_test_list() { + let rust_path = std::env::temp_dir().join("cg_gcc_read_test_list"); + let _ = remove_dir_all(&rust_path); + create_dir(rust_path.join("tests/ui")).unwrap(); + std::fs::write(rust_path.join("tests/ui/alive.rs"), "").unwrap(); + + let list_path = write_test_list(&rust_path, "\ntests/ui/alive.rs\n \n"); + let list_path = list_path.display().to_string(); + assert_eq!( + read_test_list(&rust_path, &list_path), + Ok(vec!["tests/ui/alive.rs".to_string()]) + ); + + write_test_list(&rust_path, "tests/ui/alive.rs\ntests/ui/gone.rs\n"); + let error = read_test_list(&rust_path, &list_path).unwrap_err(); + assert!(error.contains("no longer exist"), "{error}"); + assert!(error.contains("tests/ui/gone.rs"), "{error}"); + + write_test_list(&rust_path, "tests/ui/alive.rs\ntests/ui/alive.rs\n"); + let error = read_test_list(&rust_path, &list_path).unwrap_err(); + assert!(error.contains("listed more than once"), "{error}"); + assert!(error.contains("tests/ui/alive.rs"), "{error}"); + + // Directories are listed with a trailing `/`. + write_test_list(&rust_path, "tests/ui/\n"); + assert_eq!(read_test_list(&rust_path, &list_path), Ok(vec!["tests/ui/".to_string()])); + + remove_dir_all(&rust_path).unwrap(); + } +} diff --git a/compiler/rustc_codegen_gcc/build_system/src/todo.rs b/compiler/rustc_codegen_gcc/build_system/src/todo.rs new file mode 100644 index 0000000000000..5b89410844788 --- /dev/null +++ b/compiler/rustc_codegen_gcc/build_system/src/todo.rs @@ -0,0 +1,72 @@ +use std::ffi::OsStr; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::{Path, PathBuf}; +use std::process::Command; + +const EXTENSIONS: &[&str] = + &["rs", "py", "js", "sh", "c", "cpp", "h", "md", "css", "ftl", "toml", "yml", "yaml"]; + +fn has_supported_extension(path: &Path) -> bool { + path.extension().is_some_and(|ext| EXTENSIONS.iter().any(|e| ext == OsStr::new(e))) +} + +fn list_tracked_files() -> Result, String> { + let output = Command::new("git") + .args(["ls-files", "-z"]) + .output() + .map_err(|e| format!("Failed to run `git ls-files`: {e}"))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("`git ls-files` failed: {stderr}")); + } + + let mut files = Vec::new(); + for entry in output.stdout.split(|b| *b == 0) { + if entry.is_empty() { + continue; + } + let path = std::str::from_utf8(entry).unwrap(); + files.push(PathBuf::from(path)); + } + + Ok(files) +} + +pub(crate) fn run() -> Result<(), String> { + let files = list_tracked_files()?; + let mut error_count = 0; + // Avoid embedding the task marker in source so greps only find real occurrences. + let todo_marker = "todo".to_ascii_uppercase(); + + for file in files { + if !has_supported_extension(&file) { + continue; + } + + let file_handle = + File::open(&file).map_err(|e| format!("Failed to open {}: {e}", file.display()))?; + let reader = BufReader::new(file_handle); + + for (i, line) in reader.lines().enumerate() { + let line = line.map_err(|e| format!("Failed to read {}: {e}", file.display()))?; + let trimmed = line.trim(); + if trimmed.contains(&todo_marker) { + eprintln!( + "{}:{}: {} is used for tasks that should be done before merging a PR; if you want to leave a message in the codebase use FIXME", + file.display(), + i + 1, + todo_marker + ); + error_count += 1; + } + } + } + + if error_count == 0 { + return Ok(()); + } + + Err(format!("found {} {}(s)", error_count, todo_marker)) +} diff --git a/compiler/rustc_codegen_gcc/build_system/src/utils.rs b/compiler/rustc_codegen_gcc/build_system/src/utils.rs index 112322f8688c1..4c67156a85fb2 100644 --- a/compiler/rustc_codegen_gcc/build_system/src/utils.rs +++ b/compiler/rustc_codegen_gcc/build_system/src/utils.rs @@ -2,10 +2,11 @@ use std::collections::HashMap; use std::ffi::OsStr; use std::fmt::Debug; use std::fs; +use std::io::{BufReader, Read}; #[cfg(unix)] use std::os::unix::process::ExitStatusExt; use std::path::{Path, PathBuf}; -use std::process::{Command, ExitStatus, Output}; +use std::process::{Command, ExitStatus, Output, Stdio}; fn exec_command( input: &[&dyn AsRef], @@ -47,7 +48,7 @@ pub(crate) fn get_command_inner( command } -fn check_exit_status( +pub(crate) fn check_exit_status( input: &[&dyn AsRef], cwd: Option<&Path>, exit_status: ExitStatus, @@ -115,6 +116,30 @@ pub fn run_command_with_output( check_exit_status(input, cwd, exit_status, None, true) } +pub fn run_command_with_output_and_get_it( + input: &[&dyn AsRef], + cwd: Option<&Path>, +) -> Result<(ExitStatus, String), String> { + let mut child = get_command_inner(input, cwd, None) + .stderr(Stdio::piped()) + .spawn() + .map_err(|e| command_error(input, &cwd, e))?; + + let stderr = child.stderr.take().expect("Failed to capture stderr"); + let mut captured = String::new(); + BufReader::new(stderr).read_to_string(&mut captured).expect("failed to read stderr"); + + let status = child.wait().map_err(|e| command_error(input, &cwd, e))?; + #[cfg(unix)] + { + if let Some(signal) = status.signal() { + // In case the signal didn't kill the current process. + return Err(command_error(input, &cwd, format!("Process received signal {signal}"))); + } + } + Ok((status, captured)) +} + pub fn run_command_with_output_and_env( input: &[&dyn AsRef], cwd: Option<&Path>, @@ -124,7 +149,6 @@ pub fn run_command_with_output_and_env( check_exit_status(input, cwd, exit_status, None, true) } -#[cfg(not(unix))] pub fn run_command_with_output_and_env_no_err( input: &[&dyn AsRef], cwd: Option<&Path>, @@ -419,6 +443,34 @@ pub fn get_sysroot_dir() -> PathBuf { Path::new(crate::BUILD_DIR).join("build_sysroot") } +pub fn run_tool_and_install_it_if_not_present(cmd: &[&dyn AsRef]) -> Result<(), String> { + let (exit_status, stderr) = run_command_with_output_and_get_it(cmd, Some(Path::new(".")))?; + if exit_status.success() { + return Ok(()); + } + let mut iter = stderr.split('\n'); + if let Some(line) = iter.next() + && line.contains("is not installed for the toolchain") + && let Some(line) = iter.next() + && line.contains("run `rustup component add") + && let Some(cmd) = line.split('`').nth(1) + && let Some(tool_name) = cmd.rsplit(' ').next() + { + println!("`{tool_name}` is not installed for this toolchain, installing it..."); + // A weird round-about way to get a `&&str` so I can get a `&dyn AsRef` but + // as long as it works... + let cmd = cmd.split(' ').collect::>(); + let cmd = cmd.iter().map(|s: &&str| s as &dyn AsRef).collect::>(); + run_command_with_output(cmd.as_slice(), Some(Path::new(".")))?; + } else { + // If the component is installed, then it's something else. In this case we fail like we + // should have and let the user handles the error. + return check_exit_status(cmd, Some(Path::new(".")), exit_status, None, true); + } + // We retry the command... + run_command_with_output(cmd, Some(Path::new("."))) +} + #[cfg(test)] mod tests { use super::*; diff --git a/compiler/rustc_codegen_gcc/clippy.toml b/compiler/rustc_codegen_gcc/clippy.toml new file mode 100644 index 0000000000000..cf1593c691734 --- /dev/null +++ b/compiler/rustc_codegen_gcc/clippy.toml @@ -0,0 +1,3 @@ +disallowed-methods = [ + { path = "gccjit::types::Type::add_attribute", reason = "go through `type_::apply_struct_attributes` instead: an attribute set directly on a type would not be part of the `CodegenCx::struct_types` cache key, so it would silently change every other use of that type" }, +] diff --git a/compiler/rustc_codegen_gcc/doc/subtree.md b/compiler/rustc_codegen_gcc/doc/subtree.md index a81b6c9c74bdd..fcac399e46542 100644 --- a/compiler/rustc_codegen_gcc/doc/subtree.md +++ b/compiler/rustc_codegen_gcc/doc/subtree.md @@ -1,7 +1,7 @@ # git subtree sync `rustc_codegen_gcc` is a subtree of the rust compiler. As such, it needs to be -sync from time to time to ensure changes that happened on their side are also +synced from time to time to ensure changes that happened on their side are also included on our side. ### How to install a forked git-subtree @@ -41,6 +41,8 @@ cd ../rust git pull origin master git checkout -b subtree-update_cg_gcc_YYYY-MM-DD PATH="$HOME/bin:$PATH" ~/bin/git-subtree pull --prefix=compiler/rustc_codegen_gcc/ https://github.com/rust-lang/rustc_codegen_gcc.git master +# Don't forget to update the `gcc` submodule to the same version as the +# one in `rustc_codegen_gcc/libgccjit.version`. git push # Immediately merge the merge commit into cg_gcc to prevent merge conflicts when syncing from rust-lang/rust later. diff --git a/compiler/rustc_codegen_gcc/doc/tips.md b/compiler/rustc_codegen_gcc/doc/tips.md index ff92566d4a1ab..dc40ee4d39952 100644 --- a/compiler/rustc_codegen_gcc/doc/tips.md +++ b/compiler/rustc_codegen_gcc/doc/tips.md @@ -58,7 +58,7 @@ If you wish to build a custom sysroot, pass the path of your sysroot source to ` ### How to generate GIMPLE If you need to check what gccjit is generating (GIMPLE), then take a look at how to -generate it in [gimple.md](./doc/gimple.md). +generate it in [gimple.md](./gimple.md). ### How to build a cross-compiling libgccjit diff --git a/compiler/rustc_codegen_gcc/example/mini_core_hello_world.rs b/compiler/rustc_codegen_gcc/example/mini_core_hello_world.rs index 6e155f89ee5cc..ab841d51a7f53 100644 --- a/compiler/rustc_codegen_gcc/example/mini_core_hello_world.rs +++ b/compiler/rustc_codegen_gcc/example/mini_core_hello_world.rs @@ -6,7 +6,7 @@ )] #![no_core] #![allow(dead_code, internal_features, non_camel_case_types)] -#![rustfmt_skip] +#![cfg_attr(rustfmt, rustfmt_skip)] extern crate mini_core; diff --git a/compiler/rustc_codegen_gcc/libgccjit.version b/compiler/rustc_codegen_gcc/libgccjit.version index 5eef70260466f..62417a80f827e 100644 --- a/compiler/rustc_codegen_gcc/libgccjit.version +++ b/compiler/rustc_codegen_gcc/libgccjit.version @@ -1 +1 @@ -6f155cc3f5a2dff33afe6cc3ed6c2e0e605ae6a3 +badf78d09d16e66f4ca07971c51aa6a227558d4f diff --git a/compiler/rustc_codegen_gcc/patches/0001-Add-stdarch-Cargo.toml-for-testing.patch b/compiler/rustc_codegen_gcc/patches/0001-Add-stdarch-Cargo.toml-for-testing.patch deleted file mode 100644 index 3a8c37a8b8d9a..0000000000000 --- a/compiler/rustc_codegen_gcc/patches/0001-Add-stdarch-Cargo.toml-for-testing.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 190e26c9274b3c93a9ee3516b395590e6bd9213b Mon Sep 17 00:00:00 2001 -From: None -Date: Sun, 3 Aug 2025 19:54:56 -0400 -Subject: [PATCH] Patch 0001-Add-stdarch-Cargo.toml-for-testing.patch - ---- - library/stdarch/Cargo.toml | 20 ++++++++++++++++++++ - 1 file changed, 20 insertions(+) - create mode 100644 library/stdarch/Cargo.toml - -diff --git a/library/stdarch/Cargo.toml b/library/stdarch/Cargo.toml -new file mode 100644 -index 0000000..bd6725c ---- /dev/null -+++ b/library/stdarch/Cargo.toml -@@ -0,0 +1,20 @@ -+[workspace] -+resolver = "1" -+members = [ -+ "crates/*", -+ #"examples/" -+] -+exclude = [ -+ "crates/wasm-assert-instr-tests", -+ "rust_programs", -+] -+ -+[profile.release] -+debug = true -+opt-level = 3 -+incremental = true -+ -+[profile.bench] -+debug = 1 -+opt-level = 3 -+incremental = true --- -2.50.1 - diff --git a/compiler/rustc_codegen_gcc/rust-toolchain b/compiler/rustc_codegen_gcc/rust-toolchain index 56fcfdff1c719..ed97309659931 100644 --- a/compiler/rustc_codegen_gcc/rust-toolchain +++ b/compiler/rustc_codegen_gcc/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2026-04-29" +channel = "nightly-2026-09-08" components = ["rust-src", "rustc-dev", "llvm-tools-preview"] diff --git a/compiler/rustc_codegen_gcc/src/abi.rs b/compiler/rustc_codegen_gcc/src/abi.rs index 2901eb8b1a6d2..4ed30f9ba11fc 100644 --- a/compiler/rustc_codegen_gcc/src/abi.rs +++ b/compiler/rustc_codegen_gcc/src/abi.rs @@ -73,7 +73,7 @@ impl GccType for CastTarget { args.push(cx.type_ix(rem_bytes * 8)); } - cx.type_struct(&args, false) + cx.type_struct(&args, &[]) } } @@ -146,12 +146,23 @@ impl<'gcc, 'tcx> FnAbiGccExt<'gcc, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { if attrs.regular.contains(rustc_target::callconv::ArgAttribute::NonNull) { non_null_args.push(arg_index as i32 + 1); } + // There are a few others `ArgAttribute` variants" + // + // * ArgAttribute::ReadOnly: `access(read_only())`, but it's only used for emitting + // warning, not for optimization. + // * ArgAttribute::NoUndef: No equivalent in GCC + // * ArgAttribute::Writable: `access(read_write())` or `access(write_only())`, but it's + // only used for emitting warning, not for optimization. + // * ArgAttribute::NoFree: No equivalent in GCC ty }; #[cfg(not(feature = "master"))] let apply_attrs = |ty: Type<'gcc>, _attrs: &ArgAttributes, _arg_index: usize| ty; - for arg in self.args.iter() { + for (source_arg_index, arg) in self.args.iter().enumerate() { + #[cfg(not(feature = "master"))] + let _ = source_arg_index; + let arg_ty = match arg.mode { PassMode::Ignore => continue, PassMode::Pair(a, b) => { @@ -179,9 +190,31 @@ impl<'gcc, 'tcx> FnAbiGccExt<'gcc, 'tcx> for FnAbi<'tcx, Ty<'tcx>> { apply_attrs(ty, &cast.attrs, argument_tys.len()) } PassMode::Indirect { attrs: _, meta_attrs: None, on_stack: true } => { - // This is a "byval" argument, so we don't apply the `restrict` attribute on it. - on_stack_param_indices.insert(argument_tys.len()); - arg.layout.gcc_type(cx) + let x86_interrupt_first_arg = { + #[cfg(feature = "master")] + { + source_arg_index == 0 + && matches!(self.conv, CanonAbi::Interrupt(InterruptKind::X86)) + } + #[cfg(not(feature = "master"))] + { + false + } + }; + + if x86_interrupt_first_arg { + // Rust lowers the first `x86-interrupt` argument as a byval stack slot. + // LLVM represents that as a pointer parameter with `byval`; GCC's + // interrupt attribute likewise requires a pointer-shaped first parameter. + // Do not add this parameter to `on_stack_param_indices`: that set is only + // needed when GCC represents a byval argument as a value parameter, while + // this parameter is already pointer-shaped. + cx.type_ptr_to(arg.layout.gcc_type(cx)) + } else { + // This is a "byval" argument, so we don't apply the `restrict` attribute on it. + on_stack_param_indices.insert(argument_tys.len()); + arg.layout.gcc_type(cx) + } } PassMode::Direct(attrs) => { apply_attrs(arg.layout.immediate_gcc_type(cx), &attrs, argument_tys.len()) diff --git a/compiler/rustc_codegen_gcc/src/asm.rs b/compiler/rustc_codegen_gcc/src/asm.rs index 8fd438d847d29..5aebfcf583fa9 100644 --- a/compiler/rustc_codegen_gcc/src/asm.rs +++ b/compiler/rustc_codegen_gcc/src/asm.rs @@ -298,7 +298,9 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { out_place, }); - if !readwrite { + if readwrite { + self.llbb().add_assignment(None, tmp_var, in_value.immediate()); + } else { let out_gcc_idx = outputs.len() - 1; let constraint = Cow::Owned(out_gcc_idx.to_string()); @@ -364,7 +366,14 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { let ty = value.layout.gcc_type(self.cx); let reg_var = self.current_func().new_local(None, ty, "input_register"); reg_var.set_register_name(reg_name); - self.llbb().add_assignment(None, reg_var, value.immediate()); + // FIXME: We should remove this when switching to "untyped" pointers + let value = value.immediate(); + let value = if value.get_type() != ty { + self.context.new_cast(None, value, ty) + } else { + value + }; + self.llbb().add_assignment(None, reg_var, value); inputs.push(AsmInOperand { constraint: "r".into(), @@ -603,6 +612,12 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { self.llbb().add_eval(None, self.context.new_call(None, builtin_unreachable, &[])); } + if !options.contains(InlineAsmOptions::NORETURN) + && let Some(dest) = dest + { + self.switch_to_block(dest); + } + // Write results to outputs. // // We need to do this because: @@ -682,6 +697,7 @@ fn explicit_reg_to_gcc(reg: InlineAsmReg) -> &'static str { } InlineAsmReg::Arm(reg) => reg.name(), InlineAsmReg::AArch64(reg) => reg.name(), + InlineAsmReg::M68k(reg) => reg.name(), _ => unimplemented!(), } } diff --git a/compiler/rustc_codegen_gcc/src/attributes.rs b/compiler/rustc_codegen_gcc/src/attributes.rs index ce1877b308e94..9ff6c19f6f13f 100644 --- a/compiler/rustc_codegen_gcc/src/attributes.rs +++ b/compiler/rustc_codegen_gcc/src/attributes.rs @@ -2,6 +2,8 @@ use gccjit::FnAttribute; use gccjit::Function; #[cfg(feature = "master")] +use rustc_abi::{CanonAbi, InterruptKind}; +#[cfg(feature = "master")] use rustc_hir::attrs::InlineAttr; use rustc_hir::attrs::InstructionSetAttr; #[cfg(feature = "master")] @@ -9,9 +11,12 @@ use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; #[cfg(feature = "master")] use rustc_middle::mir::TerminatorKind; use rustc_middle::ty; +use rustc_target::callconv::FnAbi; #[cfg(feature = "master")] use rustc_target::spec::Arch; +#[cfg(feature = "master")] +use crate::base; use crate::context::CodegenCx; use crate::gcc_util::to_gcc_features; @@ -82,12 +87,23 @@ fn inline_attr<'gcc, 'tcx>( } } +#[cfg(feature = "master")] +fn is_x86_interrupt<'tcx>(fn_abi: Option<&FnAbi<'tcx, ty::Ty<'tcx>>>) -> bool { + matches!( + fn_abi, + Some(fn_abi) if matches!(fn_abi.conv, CanonAbi::Interrupt(InterruptKind::X86)) + ) +} + /// Composite function which sets GCC attributes for function depending on its AST (`#[attribute]`) /// attributes. pub fn from_fn_attrs<'gcc, 'tcx>( cx: &CodegenCx<'gcc, 'tcx>, #[cfg_attr(not(feature = "master"), expect(unused_variables))] func: Function<'gcc>, instance: ty::Instance<'tcx>, + #[cfg_attr(not(feature = "master"), expect(unused_variables))] fn_abi: Option< + &FnAbi<'tcx, ty::Ty<'tcx>>, + >, ) { let codegen_fn_attrs = cx.tcx.codegen_instance_attrs(instance.def); @@ -102,6 +118,16 @@ pub fn from_fn_attrs<'gcc, 'tcx>( } else { codegen_fn_attrs.inline }; + // GCC drops `weak` from a function that is also `inline`, leaving the symbol strong, and + // the linkage is what has to survive. `inline(never)` does not conflict. + let inline = match inline { + InlineAttr::Always | InlineAttr::Hint | InlineAttr::Force { .. } + if codegen_fn_attrs.linkage.is_some_and(base::linkage_needs_weak_attribute) => + { + InlineAttr::None + } + inline => inline, + }; if let Some(attr) = inline_attr(cx, inline, instance) { if let FnAttribute::AlwaysInline = attr { func.add_attribute(FnAttribute::Inline); @@ -120,6 +146,11 @@ pub fn from_fn_attrs<'gcc, 'tcx>( } } + #[cfg(feature = "master")] + let x86_interrupt = is_x86_interrupt(fn_abi); + #[cfg(not(feature = "master"))] + let x86_interrupt = false; + let mut function_features = codegen_fn_attrs .target_features .iter() @@ -135,6 +166,13 @@ pub fn from_fn_attrs<'gcc, 'tcx>( // Check if GCC requires the same. let mut global_features = cx.tcx.global_backend_features(()).iter().map(|s| s.as_str()); function_features.extend(&mut global_features); + if x86_interrupt { + // GCC does not preserve SSE, MMX, or x87 state in interrupt handlers and rejects + // them whenever those instruction sets are enabled, even if the handler does not + // emit such instructions. Restrict the function to general registers so the + // interrupt attribute works with the default x86_64 target features. + function_features.push("general-regs-only"); + } let target_features = function_features .iter() .filter_map(|feature| { diff --git a/compiler/rustc_codegen_gcc/src/back/lto.rs b/compiler/rustc_codegen_gcc/src/back/lto.rs index 98f9abdb05c4c..baf1fda02e258 100644 --- a/compiler/rustc_codegen_gcc/src/back/lto.rs +++ b/compiler/rustc_codegen_gcc/src/back/lto.rs @@ -20,6 +20,7 @@ use std::ffi::CString; use std::fs::{self, File}; use std::path::{Path, PathBuf}; +use std::sync::Arc; use gccjit::OutputKind; use object::read::archive::ArchiveFile; @@ -29,14 +30,15 @@ use rustc_codegen_ssa::back::write::{CodegenContext, FatLtoInput, SharedEmitter} use rustc_codegen_ssa::traits::*; use rustc_codegen_ssa::{CompiledModule, ModuleCodegen, ModuleKind}; use rustc_data_structures::memmap::Mmap; -use rustc_data_structures::profiling::SelfProfilerRef; use rustc_errors::{DiagCtxt, DiagCtxtHandle}; use rustc_log::tracing::info; +use rustc_session::Session; use tempfile::{TempDir, tempdir}; use crate::back::write::{codegen, save_temp_bitcode}; use crate::diagnostics::LtoBitcodeFromRlib; -use crate::{GccCodegenBackend, GccContext, LtoMode, to_gcc_opt_level}; +use crate::gcc_util::new_context; +use crate::{GccCodegenBackend, GccContext, LtoMode, SyncContext, to_gcc_opt_level}; struct LtoData { // FIXME(antoyo): use symbols_below_threshold. @@ -102,8 +104,8 @@ fn save_as_file(obj: &[u8], path: &Path) -> Result<(), LtoBitcodeFromRlib> { /// Performs fat LTO by merging all modules into a single one and returning it /// for further optimization. pub(crate) fn run_fat( + sess: &Session, cgcx: &CodegenContext, - prof: &SelfProfilerRef, shared_emitter: &SharedEmitter, each_linked_rlib_for_lto: &[PathBuf], modules: Vec>, @@ -114,8 +116,8 @@ pub(crate) fn run_fat( /*let symbols_below_threshold = lto_data.symbols_below_threshold.iter().map(|c| c.as_ptr()).collect::>();*/ fat_lto( + sess, cgcx, - prof, dcx, modules, lto_data.upstream_modules, @@ -125,15 +127,15 @@ pub(crate) fn run_fat( } fn fat_lto( + sess: &Session, cgcx: &CodegenContext, - prof: &SelfProfilerRef, dcx: DiagCtxtHandle<'_>, modules: Vec>, mut serialized_modules: Vec<(SerializedModule, CString)>, tmp_path: TempDir, //symbols_below_threshold: &[String], ) -> CompiledModule { - let _timer = prof.generic_activity("GCC_fat_lto_build_monolithic_module"); + let _timer = sess.prof.generic_activity("GCC_fat_lto_build_monolithic_module"); info!("going for a fat lto"); // Sort out all our lists of incoming modules into two lists. @@ -183,17 +185,16 @@ fn fat_lto( // module and create a linker with it. let mut module: ModuleCodegen = match costliest_module { Some((_cost, i)) => in_memory.remove(i), - None => { - unimplemented!("Incremental"); - /*assert!(!serialized_modules.is_empty(), "must have at least one serialized module"); - let (buffer, name) = serialized_modules.remove(0); - info!("no in-memory regular modules to choose from, parsing {:?}", name); - ModuleCodegen { - module_llvm: GccContext::parse(cgcx, &name, buffer.data(), dcx)?, - name: name.into_string().unwrap(), - kind: ModuleKind::Regular, - }*/ - } + None => ModuleCodegen::new_regular( + "lto_module".to_string(), + GccContext { + context: Arc::new(SyncContext::new(new_context(sess))), + relocation_model: sess.relocation_model(), + lto_supported: true, + lto_mode: LtoMode::None, + temp_dir: None, + }, + ), }; { info!("using {:?} as a base module", module.name); @@ -220,7 +221,8 @@ fn fat_lto( // We add the object files and save in should_combine_object_files that we should combine // them into a single object file when compiling later. for (bc_decoded, name) in serialized_modules { - let _timer = prof + let _timer = sess + .prof .generic_activity_with_arg_recorder("GCC_fat_lto_link_module", |recorder| { recorder.record_arg(format!("{:?}", name)) }); @@ -258,7 +260,7 @@ fn fat_lto( // of now. module.module_llvm.temp_dir = Some(tmp_path); - codegen(cgcx, prof, dcx, module, &cgcx.module_config) + codegen(cgcx, &sess.prof, dcx, module, &cgcx.module_config) } pub struct ModuleBuffer(PathBuf); diff --git a/compiler/rustc_codegen_gcc/src/back/write.rs b/compiler/rustc_codegen_gcc/src/back/write.rs index cf5514412f745..1f4fd8a314ad2 100644 --- a/compiler/rustc_codegen_gcc/src/back/write.rs +++ b/compiler/rustc_codegen_gcc/src/back/write.rs @@ -11,8 +11,8 @@ use rustc_log::tracing::debug; use rustc_session::config::OutputType; use rustc_target::spec::SplitDebuginfo; -use crate::base::add_pic_option; use crate::diagnostics::CopyBitcode; +use crate::gcc_util::add_pic_option; use crate::{GccContext, LtoMode}; pub(crate) fn codegen( @@ -60,9 +60,6 @@ pub(crate) fn codegen( let _timer = prof .generic_activity_with_arg("GCC_module_codegen_embed_bitcode", &*module.name); if lto_supported { - // FIXME(antoyo): maybe we should call embed_bitcode to have the proper iOS fixes? - //embed_bitcode(cgcx, llcx, llmod, &config.bc_cmdline, data); - context.add_command_line_option("-flto=auto"); context.add_command_line_option("-flto-partition=one"); context.add_command_line_option("-ffat-lto-objects"); diff --git a/compiler/rustc_codegen_gcc/src/base.rs b/compiler/rustc_codegen_gcc/src/base.rs index 7a25fc46fd3fc..3346ff85074d0 100644 --- a/compiler/rustc_codegen_gcc/src/base.rs +++ b/compiler/rustc_codegen_gcc/src/base.rs @@ -1,9 +1,9 @@ -use std::collections::HashSet; -use std::env; use std::sync::Arc; use std::time::Instant; -use gccjit::{CType, Context, FunctionType, GlobalKind}; +#[cfg(feature = "master")] +use gccjit::VarAttribute; +use gccjit::{CType, FunctionType, GlobalKind}; use rustc_codegen_ssa::ModuleCodegen; use rustc_codegen_ssa::base::maybe_create_entry_wrapper; use rustc_codegen_ssa::mono_item::MonoItemExt; @@ -17,11 +17,11 @@ use rustc_session::config::DebugInfo; use rustc_span::Symbol; #[cfg(feature = "master")] use rustc_target::spec::SymbolVisibility; -use rustc_target::spec::{Arch, RelocModel}; use crate::builder::Builder; use crate::context::CodegenCx; -use crate::{GccContext, LockedTargetInfo, LtoMode, SyncContext, gcc_util, new_context}; +use crate::gcc_util::new_context; +use crate::{GccContext, LockedTargetInfo, LtoMode, SyncContext}; #[cfg(feature = "master")] pub fn visibility_to_gcc(visibility: Visibility) -> gccjit::Visibility { @@ -41,32 +41,72 @@ pub fn symbol_visibility_to_gcc(visibility: SymbolVisibility) -> gccjit::Visibil } } +/// The kind of a global *definition* with an explicit `#[linkage]`. +/// +/// The flavours that another object file is allowed to override also need +/// `global_linkage_attribute` from the caller: `GlobalKind` alone cannot express weakness. pub fn global_linkage_to_gcc(linkage: Linkage) -> GlobalKind { match linkage { - Linkage::External => GlobalKind::Imported, - Linkage::AvailableExternally => GlobalKind::Imported, - Linkage::LinkOnceAny => unimplemented!(), - Linkage::LinkOnceODR => unimplemented!(), - Linkage::WeakAny => unimplemented!(), - Linkage::WeakODR => unimplemented!(), - Linkage::Internal => GlobalKind::Internal, - Linkage::ExternalWeak => GlobalKind::Imported, // FIXME(antoyo): should be weak linkage. - Linkage::Common => unimplemented!(), + Linkage::External => GlobalKind::Exported, + // libgccjit cannot emit a definition that the linker discards in favour of the one in + // another object file, so emit a private copy of it instead. + Linkage::AvailableExternally | Linkage::Internal => GlobalKind::Internal, + // libgccjit exposes no comdat, so `weak` stands in for the linkonce flavours. + Linkage::LinkOnceAny + | Linkage::LinkOnceODR + | Linkage::WeakAny + | Linkage::WeakODR + | Linkage::ExternalWeak + | Linkage::Common => GlobalKind::Exported, + } +} + +/// The attribute a global *definition* needs on top of its [`GlobalKind`] to get this linkage. +#[cfg(feature = "master")] +pub fn global_linkage_attribute<'gcc>(linkage: Linkage) -> Option> { + match linkage { + Linkage::Common => Some(VarAttribute::Common), + _ if linkage_needs_weak_attribute(linkage) => Some(VarAttribute::Weak), + _ => None, } } +/// The type of a function *definition* with an explicit `#[linkage]`. +/// +/// The flavours that another object file is allowed to override also need +/// `linkage_needs_weak_attribute` from the caller: `FunctionType` alone cannot express weakness. pub fn linkage_to_gcc(linkage: Linkage) -> FunctionType { match linkage { Linkage::External => FunctionType::Exported, - // FIXME(antoyo): set the attribute externally_visible. - Linkage::AvailableExternally => FunctionType::Extern, - Linkage::LinkOnceAny => unimplemented!(), - Linkage::LinkOnceODR => unimplemented!(), - Linkage::WeakAny => FunctionType::Exported, // FIXME(antoyo): should be similar to linkonce. - Linkage::WeakODR => unimplemented!(), - Linkage::Internal => FunctionType::Internal, - Linkage::ExternalWeak => unimplemented!(), - Linkage::Common => unimplemented!(), + // libgccjit cannot emit a definition that the linker discards in favour of the one in + // another object file, so emit a private copy of it instead. + Linkage::AvailableExternally | Linkage::Internal => FunctionType::Internal, + // libgccjit exposes no comdat, so `weak` stands in for every overridable flavour. + Linkage::LinkOnceAny + | Linkage::LinkOnceODR + | Linkage::WeakAny + | Linkage::WeakODR + | Linkage::ExternalWeak + | Linkage::Common => FunctionType::Exported, + } +} + +/// Whether a definition with this linkage must carry the `weak` attribute, so that a strong +/// definition in another object file wins over it instead of clashing with it. +/// +/// `common` is in here for functions only: GCC honours that attribute on a variable, but drops it +/// on a function, so a common function falls back to weak. Globals go through +/// `global_linkage_attribute` instead. +#[cfg(feature = "master")] +pub fn linkage_needs_weak_attribute(linkage: Linkage) -> bool { + match linkage { + Linkage::LinkOnceAny + | Linkage::LinkOnceODR + | Linkage::WeakAny + | Linkage::WeakODR + | Linkage::ExternalWeak + | Linkage::Common => true, + Linkage::External | Linkage::AvailableExternally | Linkage::Internal => false, } } @@ -101,41 +141,7 @@ pub fn compile_codegen_unit( ) -> ModuleCodegen { let cgu = tcx.codegen_unit(cgu_name); // Instantiate monomorphizations without filling out definitions yet... - let context = new_context(tcx); - - if tcx.sess.panic_strategy().unwinds() { - context.add_command_line_option("-fexceptions"); - context.add_driver_option("-fexceptions"); - } - - let disabled_features: HashSet<_> = tcx - .sess - .opts - .cg - .target_feature - .split(',') - .filter(|feature| feature.starts_with('-')) - .map(|string| &string[1..]) - .collect(); - - if !disabled_features.contains("avx") && tcx.sess.target.arch == Arch::X86_64 { - // NOTE: we always enable AVX because the equivalent of llvm.x86.sse2.cmp.pd in GCC for - // SSE2 is multiple builtins, so we use the AVX __builtin_ia32_cmppd instead. - // FIXME(antoyo): use the proper builtins for llvm.x86.sse2.cmp.pd and similar. - context.add_command_line_option("-mavx"); - } - - for arg in &tcx.sess.opts.cg.llvm_args { - context.add_command_line_option(arg); - } - // NOTE: This is needed to compile the file src/intrinsic/archs.rs during a bootstrap of rustc. - context.add_command_line_option("-fno-var-tracking-assignments"); - // NOTE: an optimization (https://github.com/rust-lang/rustc_codegen_gcc/issues/53). - context.add_command_line_option("-fno-semantic-interposition"); - // NOTE: Rust relies on LLVM not doing TBAA (https://github.com/rust-lang/unsafe-code-guidelines/issues/292). - context.add_command_line_option("-fno-strict-aliasing"); - // NOTE: Rust relies on LLVM doing wrapping on overflow. - context.add_command_line_option("-fwrapv"); + let context = new_context(tcx.sess); // NOTE: We need to honor the `#![no_builtins]` attribute to prevent GCC from // replacing code patterns (like loops) with calls to builtins (like memset). @@ -148,64 +154,6 @@ pub fn compile_codegen_unit( context.add_command_line_option("-fno-tree-loop-distribute-patterns"); } - if let Some(model) = tcx.sess.code_model() { - use rustc_target::spec::CodeModel; - - context.add_command_line_option(match model { - CodeModel::Tiny => "-mcmodel=tiny", - CodeModel::Small => "-mcmodel=small", - CodeModel::Kernel => "-mcmodel=kernel", - CodeModel::Medium => "-mcmodel=medium", - CodeModel::Large => "-mcmodel=large", - }); - } - - add_pic_option(&context, tcx.sess.relocation_model()); - - let target_cpu = gcc_util::target_cpu(tcx.sess); - if target_cpu != "generic" { - context.add_command_line_option(format!("-march={}", target_cpu)); - } - - if tcx - .sess - .opts - .unstable_opts - .function_sections - .unwrap_or(tcx.sess.target.function_sections) - { - context.add_command_line_option("-ffunction-sections"); - context.add_command_line_option("-fdata-sections"); - } - - if env::var("CG_GCCJIT_DUMP_RTL").as_deref() == Ok("1") { - context.add_command_line_option("-fdump-rtl-vregs"); - } - if env::var("CG_GCCJIT_DUMP_RTL_ALL").as_deref() == Ok("1") { - context.add_command_line_option("-fdump-rtl-all"); - } - if env::var("CG_GCCJIT_DUMP_TREE_ALL").as_deref() == Ok("1") { - context.add_command_line_option("-fdump-tree-all-eh"); - } - if env::var("CG_GCCJIT_DUMP_IPA_ALL").as_deref() == Ok("1") { - context.add_command_line_option("-fdump-ipa-all-eh"); - } - if env::var("CG_GCCJIT_DUMP_CODE").as_deref() == Ok("1") { - context.set_dump_code_on_compile(true); - } - if env::var("CG_GCCJIT_DUMP_GIMPLE").as_deref() == Ok("1") { - context.set_dump_initial_gimple(true); - } - if env::var("CG_GCCJIT_DUMP_EVERYTHING").as_deref() == Ok("1") { - context.set_dump_everything(true); - } - if env::var("CG_GCCJIT_KEEP_INTERMEDIATES").as_deref() == Ok("1") { - context.set_keep_intermediates(true); - } - if env::var("CG_GCCJIT_VERBOSE").as_deref() == Ok("1") { - context.add_driver_option("-v"); - } - // NOTE: The codegen generates unreachable blocks. context.set_allow_unreachable_blocks(true); @@ -243,6 +191,11 @@ pub fn compile_codegen_unit( // ... and now that we have everything pre-defined, fill out those definitions. for &(mono_item, item_data) in &mono_items { mono_item.define::>(&mut cx, cgu_name.as_str(), item_data); + + // Now that this function's blocks all exist, fill in the cleanup + // regions reconstructed from MIR while lowering its `invoke`s. + #[cfg(feature = "master")] + cx.populate_cleanup_regions(); } // If this codegen unit contains the main function, also create the @@ -269,24 +222,3 @@ pub fn compile_codegen_unit( (module, cost) } - -pub fn add_pic_option<'gcc>(context: &Context<'gcc>, relocation_model: RelocModel) { - match relocation_model { - rustc_target::spec::RelocModel::Static => { - context.add_command_line_option("-fno-pie"); - context.add_driver_option("-fno-pie"); - } - rustc_target::spec::RelocModel::Pic => { - context.add_command_line_option("-fPIC"); - // NOTE: we use both add_command_line_option and add_driver_option because the usage in - // this module (compile_codegen_unit) requires add_command_line_option while the usage - // in the back::write module (codegen) requires add_driver_option. - context.add_driver_option("-fPIC"); - } - rustc_target::spec::RelocModel::Pie => { - context.add_command_line_option("-fPIE"); - context.add_driver_option("-fPIE"); - } - model => eprintln!("Unsupported relocation model: {:?}", model), - } -} diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs index 88049d67964b1..dd7fc3ddc4ebb 100644 --- a/compiler/rustc_codegen_gcc/src/builder.rs +++ b/compiler/rustc_codegen_gcc/src/builder.rs @@ -4,8 +4,8 @@ use std::convert::TryFrom; use std::ops::Deref; use gccjit::{ - BinaryOp, Block, ComparisonOp, Context, Function, LValue, Location, RValue, ToRValue, Type, - UnaryOp, + BinaryOp, Block, CType, ComparisonOp, Context, Function, LValue, Location, RValue, ToRValue, + Type, UnaryOp, }; use rustc_abi as abi; use rustc_abi::{Align, HasDataLayout, Size, TargetDataLayout, WrappingRange}; @@ -36,7 +36,8 @@ use rustc_target::spec::{HasTargetSpec, HasX86AbiOpt, Target, X86Abi}; use crate::abi::FnAbiGccExt; use crate::common::{SignType, TypeReflection, type_is_pointer}; use crate::context::CodegenCx; -use crate::diagnostics; +#[cfg(feature = "master")] +use crate::context::PendingCleanup; use crate::intrinsic::llvm; use crate::type_of::LayoutGccExt; @@ -65,6 +66,33 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { self.value_counter.get() } + /// Tell GCC that `pointer` is `align`-aligned, so that the bulk memory builtins can widen their + /// accesses: a pointer cast to an aligned type would be dropped as a useless conversion. + fn assume_aligned(&mut self, pointer: RValue<'gcc>, align: Align) -> RValue<'gcc> { + if align.bytes() <= 1 { + return pointer; + } + let assume_aligned = self.context.get_builtin_function("__builtin_assume_aligned"); + let alignment = self.context.new_rvalue_from_long(self.type_size_t(), align.bytes() as i64); + let pointer_type = pointer.get_type(); + let const_void_ptr_type = self.context.new_type::<()>().make_const().make_pointer(); + let pointer = self.context.new_cast(self.location, pointer, const_void_ptr_type); + let aligned = self.context.new_call(self.location, assume_aligned, &[pointer, alignment]); + self.context.new_cast(self.location, aligned, pointer_type) + } + + /// GCC ignores a volatile qualifier on the pointers given to `memcpy`/`memmove`/`memset` and + /// happily deletes the call, so a barrier is what keeps the operation observable. The pointers + /// are fed to it because a clobber alone does not reach memory GCC believes never escapes. + fn volatile_barrier(&mut self, pointers: &[RValue<'gcc>]) { + let barrier = self.block.add_extended_asm(self.location, ""); + for pointer in pointers { + barrier.add_input_operand(None, "r", *pointer); + } + barrier.add_clobber("memory"); + barrier.set_volatile_flag(true); + } + fn atomic_extremum( &mut self, operation: ExtremumOperation, @@ -90,7 +118,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { ); let previous_var = func.new_local(self.location, previous_value.get_type(), "previous_value"); - let return_value = func.new_local(self.location, previous_value.get_type(), "return_value"); + let return_value = self.new_temp(func, self.location, previous_value.get_type()); self.llbb().add_assignment(self.location, previous_var, previous_value); self.llbb().add_assignment(self.location, return_value, previous_var.to_rvalue()); @@ -317,34 +345,59 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { self.block.get_function() } + /// Shared implementation of `call` and `tail_call`. For tail call it is important that this + /// returns a bare call, and not the result assigned to a local, or the result of `add_eval`. + fn build_call( + &mut self, + typ: Type<'gcc>, + fn_abi: Option<&FnAbi<'tcx, Ty<'tcx>>>, + func: RValue<'gcc>, + args: &[RValue<'gcc>], + funclet: Option<&Funclet>, + must_tail: bool, + ) -> RValue<'gcc> { + // FIXME(antoyo): remove when having a proper API. + let gcc_func = unsafe { std::mem::transmute::, Function<'gcc>>(func) }; + let call = if self.functions.borrow().values().any(|value| *value == gcc_func) { + // FIXME(antoyo): remove when the API supports a different type for functions. + let func: Function<'gcc> = self.cx.rvalue_as_function(func); + self.function_call(func, args, funclet, must_tail) + } else { + // If it's a not function that was defined, it's a function pointer. + self.function_ptr_call(typ, fn_abi, func, args, funclet, must_tail) + }; + if let Some(_fn_abi) = fn_abi { + // FIXME(bjorn3): Apply function attributes + } + call + } + pub fn function_call( &mut self, func: Function<'gcc>, args: &[RValue<'gcc>], _funclet: Option<&Funclet>, + must_tail: bool, ) -> RValue<'gcc> { let args = self.check_call("call", func, args); + let call = self.cx.context.new_call(self.location, func, &args); + if must_tail { + // Return the bare tail call, don't assign or `add_eval` it yet. + return call; + } + // gccjit requires to use the result of functions, even when it's not used. // That's why we assign the result to a local or call add_eval(). let return_type = func.get_return_type(); let void_type = self.context.new_type::<()>(); let current_func = self.block.get_function(); if return_type != void_type { - let result = current_func.new_local( - self.location, - return_type, - format!("returnValue{}", self.next_value_counter()), - ); - self.block.add_assignment( - self.location, - result, - self.cx.context.new_call(self.location, func, &args), - ); + let result = self.new_temp(current_func, self.location, return_type); + self.block.add_assignment(self.location, result, call); result.to_rvalue() } else { - self.block - .add_eval(self.location, self.cx.context.new_call(self.location, func, &args)); + self.block.add_eval(self.location, call); // Return dummy value when not having return value. self.context.new_rvalue_zero(self.isize_type) } @@ -357,6 +410,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { mut func_ptr: RValue<'gcc>, args: &[RValue<'gcc>], _funclet: Option<&Funclet>, + must_tail: bool, ) -> RValue<'gcc> { let func_ptr_type = { let func_ptr_type = func_ptr.get_type(); @@ -381,6 +435,12 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { let args_adjusted = args.len() != previous_arg_count; let args = self.check_ptr_call("call", func_ptr, &args, &on_stack_param_indices); + if must_tail { + // Return the bare tail call, don't assign or `add_eval` it yet. + let call = self.cx.context.new_call_through_ptr(self.location, func_ptr, &args); + return call; + } + // gccjit requires to use the result of functions, even when it's not used. // That's why we assign the result to a local or call add_eval(). let return_type = gcc_func.get_return_type(); @@ -397,11 +457,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { args_adjusted, orig_args, ); - let result = current_func.new_local( - self.location, - return_value.get_type(), - format!("ptrReturnValue{}", self.next_value_counter()), - ); + let result = self.new_temp(current_func, self.location, return_value.get_type()); self.block.add_assignment(self.location, result, return_value); result.to_rvalue() } else { @@ -423,8 +479,16 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { self.location, self.cx.context.new_call_through_ptr(self.location, func_ptr, &args), ); - // Return dummy value when not having return value. - self.context.new_rvalue_zero(self.isize_type) + // Return dummy value when not having return value, unless the intrinsic adapter + // needs to synthesize a non-void LLVM-level result from out-parameters. + llvm::adjust_intrinsic_return_value( + self, + self.context.new_rvalue_zero(self.isize_type), + &func_name, + &args, + args_adjusted, + orig_args, + ) } } @@ -439,11 +503,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { let return_type = self.context.new_type::(); let current_func = self.block.get_function(); // FIXME(antoyo): return the new_call() directly? Since the overflow function has no side-effects. - let result = current_func.new_local( - self.location, - return_type, - format!("overflowReturnValue{}", self.next_value_counter()), - ); + let result = self.new_temp(current_func, self.location, return_type); self.block.add_assignment( self.location, result, @@ -575,6 +635,18 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { default_block: Block<'gcc>, cases: impl ExactSizeIterator)>, ) { + // A switch with no cases is equivalent to an unconditional jump to the + // default block. Such a `SwitchInt` (one with only an `otherwise` target) + // is normally simplified into a `goto`, but `-Z mir-preserve-ub` keeps it, + // so it can reach here with e.g. the `bool` discriminant produced by a + // range-pattern comparison. `gcc_jit_block_end_with_switch` rejects a + // discriminant that is not of integer type, so emit a plain jump instead + // of a (pointless) switch. + if cases.len() == 0 { + self.block.end_with_jump(self.location, default_block); + return; + } + let mut gcc_cases = vec![]; let typ = self.val_ty(value); // FIXME(FractalFir): This is a workaround for a libgccjit limitation. @@ -614,24 +686,34 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { _funclet: Option<&Funclet>, instance: Option>, ) -> RValue<'gcc> { - let try_block = self.current_func().new_block("try"); + let current_func = self.current_func(); + let try_region = current_func.new_region(self.location); + let try_block = try_region.new_block("try"); let current_block = self.block; self.block = try_block; let call = self.call(typ, fn_attrs, fn_abi, func, args, None, instance); // FIXME(antoyo): use funclet here? self.block = current_block; - let return_value = - self.current_func().new_local(self.location, call.get_type(), "invokeResult"); + let return_value = self.new_temp(current_func, self.location, call.get_type()); try_block.add_assignment(self.location, return_value, call); try_block.end_with_jump(self.location, then); - if self.cleanup_blocks.borrow().contains(&catch) { - self.block.add_try_finally(self.location, try_block, catch); + if self.cx.landing_pads.borrow().contains(&catch) { + let cleanup_region = current_func.new_region(self.location); + self.block.add_cleanup(self.location, try_region, cleanup_region); + self.cx + .pending_cleanups + .borrow_mut() + .push(PendingCleanup { region: cleanup_region, landing_pad: catch }); } else { - self.block.add_try_catch(self.location, try_block, catch); + let catch_region = current_func.new_region(self.location); + for clone in gccjit::clone_blocks(&[catch]) { + catch_region.add_block(clone); + } + self.block.add_try_catch(self.location, try_region, catch_region); } self.block.end_with_jump(self.location, then); @@ -669,8 +751,9 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { if return_type == void_type { self.block.end_with_void_return(self.location) } else { - let return_value = - self.current_func().new_local(self.location, return_type, "unreachableReturn"); + let trap = self.context.get_builtin_function("__builtin_trap"); + self.block.add_eval(self.location, self.context.new_call(self.location, trap, &[])); + let return_value = self.new_temp(self.current_func(), self.location, return_type); self.block.end_with_return(self.location, return_value) } } @@ -989,11 +1072,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { // the current basic block. Otherwise, it could be used in another basic block, causing a // dereference after a drop, for instance. let deref = ptr.dereference(self.location).to_rvalue(); - let loaded_value = function.new_local( - self.location, - aligned_type, - format!("loadedValue{}", self.next_value_counter()), - ); + let loaded_value = self.new_temp(function, self.location, aligned_type); block.add_assignment(self.location, loaded_value, deref); loaded_value.to_rvalue() } @@ -1112,7 +1191,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { let next_bb = self.append_sibling_block("repeat_loop_next"); let ptr_type = start.get_type(); - let current = self.llbb().get_function().new_local(self.location, ptr_type, "loop_var"); + let current = self.new_temp(self.llbb().get_function(), self.location, ptr_type); let current_val = current.to_rvalue(); self.assign(current, start); @@ -1403,47 +1482,53 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { fn memcpy( &mut self, dst: RValue<'gcc>, - _dst_align: Align, + dst_align: Align, src: RValue<'gcc>, - _src_align: Align, + src_align: Align, size: RValue<'gcc>, flags: MemFlags, _tt: Option, // Autodiff TypeTrees are LLVM-only, ignored in GCC backend ) { assert!(!flags.contains(MemFlags::NONTEMPORAL), "non-temporal memcpy not supported"); let size = self.intcast(size, self.type_size_t(), false); - let _is_volatile = flags.contains(MemFlags::VOLATILE); let dst = self.pointercast(dst, self.type_i8p()); + let dst = self.assume_aligned(dst, dst_align); let src = self.pointercast(src, self.type_ptr_to(self.type_void())); + let src = self.assume_aligned(src, src_align); let memcpy = self.context.get_builtin_function("memcpy"); - // FIXME(antoyo): handle aligns and is_volatile. self.block.add_eval( self.location, self.context.new_call(self.location, memcpy, &[dst, src, size]), ); + if flags.contains(MemFlags::VOLATILE) { + self.volatile_barrier(&[dst, src]); + } } fn memmove( &mut self, dst: RValue<'gcc>, - _dst_align: Align, + dst_align: Align, src: RValue<'gcc>, - _src_align: Align, + src_align: Align, size: RValue<'gcc>, flags: MemFlags, ) { assert!(!flags.contains(MemFlags::NONTEMPORAL), "non-temporal memmove not supported"); let size = self.intcast(size, self.type_size_t(), false); - let _is_volatile = flags.contains(MemFlags::VOLATILE); let dst = self.pointercast(dst, self.type_i8p()); + let dst = self.assume_aligned(dst, dst_align); let src = self.pointercast(src, self.type_ptr_to(self.type_void())); + let src = self.assume_aligned(src, src_align); let memmove = self.context.get_builtin_function("memmove"); - // FIXME(antoyo): handle is_volatile. self.block.add_eval( self.location, self.context.new_call(self.location, memmove, &[dst, src, size]), ); + if flags.contains(MemFlags::VOLATILE) { + self.volatile_barrier(&[dst, src]); + } } fn memset( @@ -1451,20 +1536,22 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { ptr: RValue<'gcc>, fill_byte: RValue<'gcc>, size: RValue<'gcc>, - _align: Align, + align: Align, flags: MemFlags, ) { assert!(!flags.contains(MemFlags::NONTEMPORAL), "non-temporal memset not supported"); - let _is_volatile = flags.contains(MemFlags::VOLATILE); let ptr = self.pointercast(ptr, self.type_i8p()); + let ptr = self.assume_aligned(ptr, align); let memset = self.context.get_builtin_function("memset"); - // FIXME(antoyo): handle align and is_volatile. let fill_byte = self.context.new_cast(self.location, fill_byte, self.i32_type); let size = self.intcast(size, self.type_size_t(), false); self.block.add_eval( self.location, self.context.new_call(self.location, memset, &[ptr, fill_byte, size]), ); + if flags.contains(MemFlags::VOLATILE) { + self.volatile_barrier(&[ptr]); + } } fn vscale(&mut self, _: Self::Type) -> Self::Value { @@ -1478,7 +1565,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { mut else_val: RValue<'gcc>, ) -> RValue<'gcc> { let func = self.current_func(); - let variable = func.new_local(self.location, then_val.get_type(), "selectVar"); + let variable = self.new_temp(func, self.location, then_val.get_type()); let then_block = func.new_block("then"); let else_block = func.new_block("else"); let after_block = func.new_block("after"); @@ -1500,8 +1587,10 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { variable.to_rvalue() } - fn va_arg(&mut self, _list: RValue<'gcc>, _ty: Type<'gcc>) -> RValue<'gcc> { - unimplemented!(); + fn va_arg(&mut self, list: RValue<'gcc>, ty: Type<'gcc>) -> RValue<'gcc> { + let va_list_type = self.context.new_c_type(CType::VaList); + let list = self.context.new_cast(self.location, list, va_list_type.make_pointer()); + self.context.new_va_arg(self.location, list, ty) } #[cfg(feature = "master")] @@ -1604,45 +1693,32 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { // NOTE: insert the current block in a variable so that a later call to invoke knows to // generate a try/finally instead of a try/catch for this block. - self.cleanup_blocks.borrow_mut().insert(self.block); - - let eh_pointer_builtin = - self.cx.context.get_target_builtin_function("__builtin_eh_pointer"); - let zero = self.cx.context.new_rvalue_zero(self.int_type); - let ptr = self.cx.context.new_call(self.location, eh_pointer_builtin, &[zero]); - - let value1_type = self.u8_type.make_pointer(); - let ptr = self.cx.context.new_cast(self.location, ptr, value1_type); - let value1 = ptr; - let value2 = zero; // FIXME(antoyo): set the proper value here (the type of exception?). + self.cx.landing_pads.borrow_mut().insert(self.block); + // A cleanup resumes by falling through: it never inspects the exception + // object. + let value1 = self.context.new_null(self.u8_type.make_pointer()); + let value2 = self.context.new_rvalue_zero(self.i32_type); (value1, value2) } #[cfg(not(feature = "master"))] fn cleanup_landing_pad(&mut self, _pers_fn: Function<'gcc>) -> (RValue<'gcc>, RValue<'gcc>) { let value1 = self - .current_func() - .new_local(self.location, self.u8_type.make_pointer(), "landing_pad0") + .new_temp(self.current_func(), self.location, self.u8_type.make_pointer()) .to_rvalue(); - let value2 = - self.current_func().new_local(self.location, self.i32_type, "landing_pad1").to_rvalue(); + let value2 = self.new_temp(self.current_func(), self.location, self.i32_type).to_rvalue(); (value1, value2) } fn filter_landing_pad(&mut self, pers_fn: Function<'gcc>) { - // FIXME(antoyo): generate the correct landing pad - self.cleanup_landing_pad(pers_fn); + self.set_personality_fn(pers_fn); } #[cfg(feature = "master")] - fn resume(&mut self, exn0: RValue<'gcc>, _exn1: RValue<'gcc>) { - let exn_type = exn0.get_type(); - let exn = self.context.new_cast(self.location, exn0, exn_type); - let unwind_resume = self.context.get_target_builtin_function("__builtin_unwind_resume"); - self.llbb() - .add_eval(self.location, self.context.new_call(self.location, unwind_resume, &[exn])); - self.unreachable(); + fn resume(&mut self, _exn0: RValue<'gcc>, _exn1: RValue<'gcc>) { + // End the cleanup by falling off the end of its region body. + self.block.end_with_fallthrough(self.location); } #[cfg(not(feature = "master"))] @@ -1694,7 +1770,7 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { // NOTE: since success contains the call to the intrinsic, it must be added to the basic block before // expected so that we store expected after the call. - let success_var = self.current_func().new_local(self.location, self.bool_type, "success"); + let success_var = self.new_temp(self.current_func(), self.location, self.bool_type); self.llbb().add_assignment(self.location, success_var, success); (expected.to_rvalue(), success_var.to_rvalue()) @@ -1783,34 +1859,34 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { funclet: Option<&Funclet>, _instance: Option>, ) -> RValue<'gcc> { - // FIXME(antoyo): remove when having a proper API. - let gcc_func = unsafe { std::mem::transmute::, Function<'gcc>>(func) }; - let call = if self.functions.borrow().values().any(|value| *value == gcc_func) { - // FIXME(antoyo): remove when the API supports a different type for functions. - let func: Function<'gcc> = self.cx.rvalue_as_function(func); - self.function_call(func, args, funclet) - } else { - // If it's a not function that was defined, it's a function pointer. - self.function_ptr_call(typ, fn_abi, func, args, funclet) - }; - if let Some(_fn_abi) = fn_abi { - // FIXME(bjorn3): Apply function attributes - } - call + self.build_call(typ, fn_abi, func, args, funclet, false) } fn tail_call( &mut self, - _llty: Self::Type, + llty: Self::Type, _fn_attrs: Option<&CodegenFnAttrs>, - _fn_abi: &FnAbi<'tcx, Ty<'tcx>>, - _llfn: Self::Value, - _args: &[Self::Value], - _funclet: Option<&Self::Funclet>, + fn_abi: &FnAbi<'tcx, Ty<'tcx>>, + llfn: Self::Value, + args: &[Self::Value], + funclet: Option<&Self::Funclet>, _instance: Option>, ) { - // FIXME: implement support for explicit tail calls like rustc_codegen_llvm. - self.tcx.dcx().emit_fatal(diagnostics::ExplicitTailCallsUnsupported); + // `emit_call` returns a bare call for here, it has not been assigned or passed to add_eval. + let call = self.build_call(llty, Some(fn_abi), llfn, args, funclet, true); + call.set_require_tail_call(true); + + let return_type = self.current_func().get_return_type(); + let void_type = self.context.new_type::<()>(); + + if return_type == void_type { + // For a void return the call is emitted as its own statement, immediately + // followed by a void return, so the tail call sits in tail position. + self.llbb().add_eval(self.location, call); + self.ret_void(); + } else { + self.ret(call) + } } fn zext(&mut self, value: RValue<'gcc>, dest_typ: Type<'gcc>) -> RValue<'gcc> { @@ -2395,11 +2471,31 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { self.bitcast_if_needed(res, result_type) } + /// Create a temporary variable. + /// + /// GCC will use more stack space with a local variable than with a temporary variable in debug mode, + /// so in order to avoid having the stack probe test fail in CI, we avoid creating local variables for temporaries. + pub fn new_temp( + &self, + function: Function<'gcc>, + location: Option>, + typ: Type<'gcc>, + ) -> LValue<'gcc> { + #[cfg(feature = "master")] + { + function.new_temp(location, typ) + } + #[cfg(not(feature = "master"))] + { + function.new_local(location, typ, format!("temp{}", self.next_value_counter())) + } + } + // GCC doesn't like deeply nested expressions. // By assigning intermediate expressions to a variable, this allow us to avoid deeply nested // expressions and GCC will use much less RAM. fn assign_to_var(&self, value: RValue<'gcc>) -> RValue<'gcc> { - let var = self.current_func().new_local(self.location, value.get_type(), "opResult"); + let var = self.new_temp(self.current_func(), self.location, value.get_type()); self.llbb().add_assignment(self.location, var, value); var.to_rvalue() } diff --git a/compiler/rustc_codegen_gcc/src/callee.rs b/compiler/rustc_codegen_gcc/src/callee.rs index 00f095ed54371..d3f412180da55 100644 --- a/compiler/rustc_codegen_gcc/src/callee.rs +++ b/compiler/rustc_codegen_gcc/src/callee.rs @@ -70,7 +70,7 @@ pub fn get_fn<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, instance: Instance<'tcx>) cx.linkage.set(FunctionType::Extern); let func = cx.declare_fn(sym, fn_abi); - attributes::from_fn_attrs(cx, func, instance); + attributes::from_fn_attrs(cx, func, instance, Some(fn_abi)); #[cfg(feature = "master")] { diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index 6bd186f1121fc..21d92c6cc2936 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -12,6 +12,7 @@ use rustc_session::PointerAuthSchema; use crate::consts::const_alloc_to_gcc; use crate::context::{CodegenCx, new_array_type}; +use crate::type_::struct_attributes; use crate::type_of::LayoutGccExt; impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { @@ -125,78 +126,86 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { } } +/// The element type and element count of the array used to represent a run of `len` constant bytes. +/// +/// Larger integers are used where possible: this reduces the number of rvalues, which is a +/// significant memory saving on constant-heavy crates. +fn byte_run_shape<'gcc>(cx: &CodegenCx<'gcc, '_>, len: usize) -> (Type<'gcc>, u64) { + match len % 8 { + 0 => (cx.context.new_type::(), len as u64 / 8), + 4 => (cx.context.new_type::(), len as u64 / 4), + _ => (cx.context.new_type::(), len as u64), + } +} + +/// The type [`bytes_in_context`] gives a run of `len` constant bytes. +/// +/// Exposed separately so that the type of a constant allocation can be computed before any of its +/// rvalues exist; see [`crate::consts::const_alloc_type`]. +/// +/// The result is cached because `gcc_jit_context_new_array_type` mints a fresh type every call. +/// Two equal-but-distinct array types would key [`CodegenCx::type_struct`] differently and so +/// produce two distinct anonymous structs, and libgccjit compares struct types by identity. +pub fn bytes_type_in_context<'gcc>(cx: &CodegenCx<'gcc, '_>, len: usize) -> Type<'gcc> { + let (element_type, count) = byte_run_shape(cx, len); + if let Some(&typ) = cx.byte_array_types.borrow().get(&(element_type, count)) { + return typ; + } + let typ = new_array_type(cx.context, None, element_type, count); + cx.byte_array_types.borrow_mut().insert((element_type, count), typ); + typ +} + +// FIXME(FractalFir): Consider using `global_set_initializer` instead. Before this is done, we need to confirm that +// `global_set_initializer` is more memory efficient than the current solution. +// `global_set_initializer` calls `global_set_initializer_rvalue` under the hood - does it generate an array of rvalues, +// or is it using a more efficient representation? pub fn bytes_in_context<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, bytes: &[u8]) -> RValue<'gcc> { - // Instead of always using an array of bytes, use an array of larger integers of target endianness - // if possible. This reduces the amount of `rvalues` we use, which reduces memory usage significantly. - // - // FIXME(FractalFir): Consider using `global_set_initializer` instead. Before this is done, we need to confirm that - // `global_set_initializer` is more memory efficient than the current solution. - // `global_set_initializer` calls `global_set_initializer_rvalue` under the hood - does it generate an array of rvalues, - // or is it using a more efficient representation? - match bytes.len() % 8 { + let typ = bytes_type_in_context(cx, bytes.len()); + let (element_type, _) = byte_run_shape(cx, bytes.len()); + let context = &cx.context; + // Since we are representing arbitrary byte runs as integers, we need to follow the target + // endianness. + let endian = cx.sess().target.options.endian; + let elements: Vec<_> = match bytes.len() % 8 { 0 => { - debug_assert_eq!( - bytes.len() % 8, - 0, - "bytes length is not a multiple of 8, so bytes.as_chunks will have a remainder" - ); - let context = &cx.context; - let byte_type = context.new_type::(); - let typ = new_array_type(context, None, byte_type, bytes.len() as u64 / 8); - let elements: Vec<_> = bytes - .as_chunks::<8>() - .0 + let (arrays, remainder) = bytes.as_chunks::<8>(); + debug_assert!(remainder.is_empty()); + arrays .iter() .map(|&arr| { context.new_rvalue_from_long( - byte_type, - // Since we are representing arbitrary byte runs as integers, we need to follow the target - // endianness. - match cx.sess().target.options.endian { + element_type, + match endian { rustc_abi::Endian::Little => u64::from_le_bytes(arr) as i64, rustc_abi::Endian::Big => u64::from_be_bytes(arr) as i64, }, ) }) - .collect(); - context.new_array_constructor(None, typ, &elements) + .collect() } 4 => { - debug_assert_eq!( - bytes.len() % 4, - 0, - "bytes length is not a multiple of 4, so bytes.as_chunks will have a remainder" - ); - let context = &cx.context; - let byte_type = context.new_type::(); - let typ = new_array_type(context, None, byte_type, bytes.len() as u64 / 4); - let elements: Vec<_> = bytes - .as_chunks::<4>() - .0 + let (arrays, remainder) = bytes.as_chunks::<4>(); + debug_assert!(remainder.is_empty()); + arrays .iter() .map(|&arr| { context.new_rvalue_from_int( - byte_type, - match cx.sess().target.options.endian { + element_type, + match endian { rustc_abi::Endian::Little => u32::from_le_bytes(arr) as i32, rustc_abi::Endian::Big => u32::from_be_bytes(arr) as i32, }, ) }) - .collect(); - context.new_array_constructor(None, typ, &elements) - } - _ => { - let context = cx.context; - let byte_type = context.new_type::(); - let typ = new_array_type(context, None, byte_type, bytes.len() as u64); - let elements: Vec<_> = bytes - .iter() - .map(|&byte| context.new_rvalue_from_int(byte_type, byte as i32)) - .collect(); - context.new_array_constructor(None, typ, &elements) + .collect() } - } + _ => bytes + .iter() + .map(|&byte| context.new_rvalue_from_int(element_type, byte as i32)) + .collect(), + }; + context.new_array_constructor(None, typ, &elements) } pub fn type_is_pointer(typ: Type<'_>) -> bool { @@ -298,7 +307,7 @@ impl<'gcc, 'tcx> ConstCodegenMethods for CodegenCx<'gcc, 'tcx> { fn const_struct(&self, values: &[RValue<'gcc>], packed: bool) -> RValue<'gcc> { let fields: Vec<_> = values.iter().map(|value| value.get_type()).collect(); // FIXME(antoyo): cache the type? It's anonymous, so probably not. - let typ = self.type_struct(&fields, packed); + let typ = self.type_struct(&fields, &struct_attributes(packed, None)); let struct_type = typ.is_struct().expect("struct type"); self.context.new_struct_constructor(None, struct_type.as_type(), None, values) } diff --git a/compiler/rustc_codegen_gcc/src/consts.rs b/compiler/rustc_codegen_gcc/src/consts.rs index 42ff930968501..06e945a6a4576 100644 --- a/compiler/rustc_codegen_gcc/src/consts.rs +++ b/compiler/rustc_codegen_gcc/src/consts.rs @@ -1,3 +1,5 @@ +use std::ops::Range; + #[cfg(feature = "master")] use gccjit::{FnAttribute, VarAttribute, Visibility}; use gccjit::{Function, GlobalKind, LValue, RValue, ToRValue, Type}; @@ -11,15 +13,18 @@ use rustc_hir::def_id::LOCAL_CRATE; use rustc_log::tracing::trace; use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}; use rustc_middle::mir::interpret::{ - self, ConstAllocation, ErrorHandled, Scalar as InterpScalar, read_target_uint, + self, Allocation, ConstAllocation, CtfeProvenance, ErrorHandled, Scalar as InterpScalar, + read_target_uint, }; +use rustc_middle::mono::MonoItem; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::{self, Instance}; use rustc_middle::{bug, span_bug}; use rustc_span::def_id::DefId; -use crate::base; +use crate::common::bytes_type_in_context; use crate::context::CodegenCx; +use crate::type_::struct_attributes; use crate::type_of::LayoutGccExt; pub(crate) fn const_alloc_to_gcc<'gcc, 'tcx>( @@ -99,15 +104,21 @@ impl<'gcc, 'tcx> StaticCodegenMethods for CodegenCx<'gcc, 'tcx> { let is_thread_local = attrs.flags.contains(CodegenFnAttrFlags::THREAD_LOCAL); let global = self.get_static_inner(def_id, val_llty); - #[cfg(feature = "master")] - if global.to_rvalue().get_type() != val_llty { - global.to_rvalue().set_type(val_llty); - } + debug_assert_eq!( + global.to_rvalue().get_type(), + val_llty, + "`predefine_static` declared this global with a type its initializer does not have" + ); // NOTE: Alignment from attributes has already been applied to the allocation. set_global_alignment(self, global, alloc.align); - global.global_set_initializer_rvalue(value); + // A common symbol is storage the linker allocates and zero-fills, so giving the definition + // an initializer — even an all-zero one — takes it back out of `.comm`. A non-zero one is + // kept: the symbol is then an ordinary definition, which is what GCC does with it too. + if attrs.linkage != Some(Linkage::Common) || !is_zero_initializer(alloc) { + global.global_set_initializer_rvalue(value); + } // As an optimization, all shared statics which do not have interior // mutability are placed into read-only memory. @@ -160,29 +171,52 @@ impl<'gcc, 'tcx> StaticCodegenMethods for CodegenCx<'gcc, 'tcx> { } // Wasm statics with custom link sections get special treatment as they - // go into custom sections of the wasm executable. - if self.tcx.sess.target.is_like_wasm { + // go into custom sections of the wasm executable. The exception to this + // is the `.init_array` section which are treated specially by the wasm linker. + if self.tcx.sess.target.is_like_wasm + && attrs + .link_section + .map(|link_section| !link_section.as_str().starts_with(".init_array")) + .unwrap_or(true) + { if let Some(_section) = attrs.link_section { unimplemented!(); } - } else { - // FIXME(antoyo): set link section. + } else if let Some(_section) = attrs.link_section { + #[cfg(feature = "master")] + global.add_attribute(VarAttribute::Section(_section.as_str())); } - if attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER) - || attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) - { - self.add_used_global(global.to_rvalue()); + if attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER) { + // To copy the conditions from the LLVM backend... + assert!(!attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER)); + self.add_used_global(global); + } + if attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) { + // To copy the conditions from the LLVM backend... + assert!(!attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER)); + self.add_retained_global(global); } } } impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { - /// Add a global value to a list to be stored in the `llvm.used` variable, an array of i8*. - pub fn add_used_global(&mut self, _global: RValue<'gcc>) { - // FIXME(antoyo) + /// Need to have the `SHF_GNU_RETAIN` flag, so needs to use the `retain` attribute instead of + /// `used`. This is used by `#[used(linker)]`. + pub fn add_retained_global(&mut self, global: LValue<'gcc>) { + // We need to add the `used` C attribute in any case. + self.add_used_global(global); + #[cfg(feature = "master")] + global.add_attribute(VarAttribute::Retain); + } + + /// This is used by `#[used(compiler)]` and `#[used]`. + pub fn add_used_global(&mut self, _global: LValue<'gcc>) { + #[cfg(feature = "master")] + _global.add_attribute(VarAttribute::Used); } + // No need to have the `SHF_GNU_RETAIN` flag, so `used` attribute is ok. #[cfg_attr(not(feature = "master"), expect(unused_variables))] pub fn add_used_function(&self, function: Function<'gcc>) { #[cfg(feature = "master")] @@ -237,15 +271,14 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { return global; } - // FIXME: Once we stop removing globals in `codegen_static`, we can uncomment this code. - // let defined_in_current_codegen_unit = - // self.codegen_unit.items().contains_key(&MonoItem::Static(def_id)); - // assert!( - // !defined_in_current_codegen_unit, - // "consts::get_static() should always hit the cache for \ - // statics defined in the same CGU, but did not for `{:?}`", - // def_id - // ); + let defined_in_current_codegen_unit = + self.codegen_unit.items().contains_key(&MonoItem::Static(def_id)); + assert!( + !defined_in_current_codegen_unit, + "consts::get_static() should always hit the cache for \ + statics defined in the same CGU, but did not for `{:?}`", + def_id + ); let sym = self.tcx.symbol_name(instance).name; let fn_attrs = self.tcx.codegen_fn_attrs(def_id); @@ -309,75 +342,133 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { global } } -/// Converts a given const alloc to a gcc Rvalue, without any caching or deduplication. -/// YOU SHOULD NOT call this function directly - that may break the semantics of Rust. -/// Use `const_data_from_alloc` instead. -pub(crate) fn const_alloc_to_gcc_uncached<'gcc>( - cx: &CodegenCx<'gcc, '_>, - alloc: ConstAllocation<'_>, -) -> RValue<'gcc> { - let alloc = alloc.inner(); - let mut llvals = Vec::with_capacity(alloc.provenance().ptrs().len() + 1); - let dl = cx.data_layout(); - let pointer_size = dl.pointer_size().bytes() as usize; +/// One field of the packed struct that a constant allocation is lowered to. +enum AllocField { + /// A run of bytes carrying no provenance. + Bytes { range: Range }, + /// A pointer with provenance, occupying one target pointer worth of bytes. + Pointer { offset: usize, prov: CtfeProvenance }, +} + +/// The field-by-field shape of `alloc`. +/// +/// [`const_alloc_to_gcc_uncached`] and [`const_alloc_type`] have to agree exactly on this, down to +/// the empty trailing run an allocation ending on a pointer produces, so both derive the shape here +/// instead of each walking the allocation on its own. +fn alloc_fields(cx: &CodegenCx<'_, '_>, alloc: &interpret::Allocation) -> Vec { + let pointer_size = cx.data_layout().pointer_size().bytes() as usize; + let mut fields = Vec::with_capacity(alloc.provenance().ptrs().len() + 1); let mut next_offset = 0; for &(offset, prov) in alloc.provenance().ptrs().iter() { - let alloc_id = prov.alloc_id(); let offset = offset.bytes(); assert_eq!(offset as usize as u64, offset); let offset = offset as usize; if offset > next_offset { - // This `inspect` is okay since we have checked that it is not within a pointer with provenance, it - // is within the bounds of the allocation, and it doesn't affect interpreter execution - // (we inspect the result after interpreter execution). Any undef byte is replaced with - // some arbitrary byte value. - // - // FIXME: relay undef bytes to codegen as undef const bytes - let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter(next_offset..offset); - llvals.push(cx.const_bytes(bytes)); + fields.push(AllocField::Bytes { range: next_offset..offset }); } - let ptr_offset = read_target_uint( - dl.endian, - // This `inspect` is okay since it is within the bounds of the allocation, it doesn't - // affect interpreter execution (we inspect the result after interpreter execution), - // and we properly interpret the provenance as a relocation pointer offset. - alloc.inspect_with_uninit_and_ptr_outside_interpreter(offset..(offset + pointer_size)), - ) - .expect("const_alloc_to_gcc_uncached: could not read relocation pointer") - as u64; - - let address_space = cx.tcx.global_alloc(alloc_id).address_space(cx); - - llvals.push(cx.scalar_to_backend( - InterpScalar::from_pointer( - interpret::Pointer::new(prov, Size::from_bytes(ptr_offset)), - &cx.tcx, - ), - abi::Scalar::Initialized { - value: Primitive::Pointer(address_space), - valid_range: WrappingRange::full(dl.pointer_size()), - }, - cx.type_i8p_ext(address_space), - )); + fields.push(AllocField::Pointer { offset, prov }); next_offset = offset + pointer_size; } if alloc.len() >= next_offset { - let range = next_offset..alloc.len(); - // This `inspect` is okay since we have check that it is after all provenance, it is - // within the bounds of the allocation, and it doesn't affect interpreter execution (we - // inspect the result after interpreter execution). Any undef byte is replaced with some - // arbitrary byte value. - // - // FIXME: relay undef bytes to codegen as undef const bytes - let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter(range); - llvals.push(cx.const_bytes(bytes)); + fields.push(AllocField::Bytes { range: next_offset..alloc.len() }); } + fields +} + +/// The type [`const_alloc_to_gcc`] gives `alloc`, computed without building any rvalue. +/// +/// This lets `predefine_static` declare a static's global with the type its initializer will have, +/// so that the two never disagree. It must not reach for the rvalue of anything it points at: +/// during the predefine pass the pointee may not be declared yet, and `alloc_to_backend` would +/// declare it with the wrong type behind our back. +pub(crate) fn const_alloc_type<'gcc>( + cx: &CodegenCx<'gcc, '_>, + alloc: ConstAllocation<'_>, +) -> Type<'gcc> { + let fields: Vec<_> = alloc_fields(cx, alloc.inner()) + .into_iter() + .map(|field| match field { + AllocField::Bytes { range } => bytes_type_in_context(cx, range.len()), + AllocField::Pointer { prov, .. } => { + let address_space = cx.tcx.global_alloc(prov.alloc_id()).address_space(cx); + cx.type_i8p_ext(address_space) + } + }) + .collect(); + cx.type_struct(&fields, &struct_attributes(true, None)) +} + +/// Converts a given const alloc to a gcc Rvalue, without any caching or deduplication. +/// YOU SHOULD NOT call this function directly - that may break the semantics of Rust. +/// Use `const_data_from_alloc` instead. +pub(crate) fn const_alloc_to_gcc_uncached<'gcc>( + cx: &CodegenCx<'gcc, '_>, + alloc: ConstAllocation<'_>, +) -> RValue<'gcc> { + let alloc = alloc.inner(); + let dl = cx.data_layout(); + let pointer_size = dl.pointer_size(); + + let llvals: Vec<_> = alloc_fields(cx, alloc) + .into_iter() + .map(|field| match field { + AllocField::Bytes { range } => { + // This `inspect` is okay since we have checked that it is not within a pointer with + // provenance, it is within the bounds of the allocation, and it doesn't affect + // interpreter execution (we inspect the result after interpreter execution). Any + // undef byte is replaced with some arbitrary byte value. + // + // FIXME: relay undef bytes to codegen as undef const bytes + cx.const_bytes(alloc.inspect_with_uninit_and_ptr_outside_interpreter(range)) + } + AllocField::Pointer { offset, prov } => { + let ptr_offset = read_target_uint( + dl.endian, + // This `inspect` is okay since it is within the bounds of the allocation, it + // doesn't affect interpreter execution (we inspect the result after interpreter + // execution), and we properly interpret the provenance as a relocation pointer + // offset. + alloc.inspect_with_uninit_and_ptr_outside_interpreter( + offset..(offset + pointer_size.bytes() as usize), + ), + ) + .expect("const_alloc_to_gcc_uncached: could not read relocation pointer") + as u64; + + let address_space = cx.tcx.global_alloc(prov.alloc_id()).address_space(cx); + + cx.scalar_to_backend( + InterpScalar::from_pointer( + interpret::Pointer::new(prov, Size::from_bytes(ptr_offset)), + &cx.tcx, + ), + abi::Scalar::Initialized { + value: Primitive::Pointer(address_space), + valid_range: WrappingRange::full(pointer_size), + }, + cx.type_i8p_ext(address_space), + ) + } + }) + .collect(); + // FIXME(bjorn3) avoid wrapping in a struct when there is only a single element. cx.const_struct(&llvals, true) } +/// Whether this allocation is all zeroes, and so needs no initializer to be spelled out. +fn is_zero_initializer(alloc: &Allocation) -> bool { + alloc.provenance().ptrs().is_empty() + // This `inspect` is okay: it is within the bounds of the allocation, there is no provenance + // to misread, and it does not affect interpreter execution. + && alloc + .inspect_with_uninit_and_ptr_outside_interpreter(0..alloc.size().bytes_usize()) + .iter() + .all(|&byte| byte == 0) +} + fn codegen_static_initializer<'gcc, 'tcx>( cx: &CodegenCx<'gcc, 'tcx>, def_id: DefId, @@ -394,10 +485,10 @@ fn check_and_apply_linkage<'gcc, 'tcx>( ) -> LValue<'gcc> { let is_tls = attrs.flags.contains(CodegenFnAttrFlags::THREAD_LOCAL); if let Some(linkage) = attrs.import_linkage { - // Declare a symbol `foo` with the desired linkage. - let global1 = - cx.declare_global_with_linkage(sym, cx.type_i8(), base::global_linkage_to_gcc(linkage)); + // Whatever the flavour, an import is an undefined reference to a symbol defined elsewhere. + let global1 = cx.declare_global_with_linkage(sym, cx.type_i8(), GlobalKind::Imported); + // Only `extern_weak` lets the symbol stay unresolved, in which case it reads as null. if linkage == Linkage::ExternalWeak { #[cfg(feature = "master")] global1.add_attribute(VarAttribute::Weak); @@ -411,8 +502,13 @@ fn check_and_apply_linkage<'gcc, 'tcx>( // zero. let real_name = format!("_rust_extern_with_linkage_{:016x}_{sym}", cx.tcx.stable_crate_id(LOCAL_CRATE)); - let global2 = cx.define_global(&real_name, gcc_type, is_tls, attrs.link_section); - // FIXME(antoyo): set linkage. + let global2 = cx.define_global( + &real_name, + gcc_type, + GlobalKind::Internal, + is_tls, + attrs.link_section, + ); let value = cx.const_ptrcast(global1.get_address(None), gcc_type); global2.global_set_initializer_rvalue(value); global2 diff --git a/compiler/rustc_codegen_gcc/src/context.rs b/compiler/rustc_codegen_gcc/src/context.rs index 19fbe37c27b9e..1453ea012f29d 100644 --- a/compiler/rustc_codegen_gcc/src/context.rs +++ b/compiler/rustc_codegen_gcc/src/context.rs @@ -1,6 +1,8 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; +#[cfg(feature = "master")] +use gccjit::Region; use gccjit::{Block, CType, Context, Function, FunctionType, LValue, Location, RValue, Type}; use rustc_abi::{Align, HasDataLayout, PointeeInfo, Size, TargetDataLayout, VariantIdx}; use rustc_codegen_ssa::base::wants_msvc_seh; @@ -26,6 +28,13 @@ use rustc_target::spec::{HasTargetSpec, HasX86AbiOpt, Target, TlsModel, X86Abi}; use crate::abi::conv_to_fn_attribute; use crate::callee::get_fn; use crate::common::SignType; +use crate::type_::StructTypeKey; + +#[cfg(feature = "master")] +pub struct PendingCleanup<'gcc> { + pub region: Region<'gcc>, + pub landing_pad: Block<'gcc>, +} #[cfg_attr(not(feature = "master"), expect(dead_code))] pub struct CodegenCx<'gcc, 'tcx> { @@ -85,7 +94,14 @@ pub struct CodegenCx<'gcc, 'tcx> { pub types: RefCell, Option), Type<'gcc>>>, pub tcx: TyCtxt<'tcx>, - pub struct_types: RefCell>, Type<'gcc>>>, + /// Cache of the anonymous struct types. + pub struct_types: RefCell, Type<'gcc>>>, + + /// Cache of the array types used for runs of constant bytes, keyed by element type and count. + /// + /// libgccjit mints a fresh type on every `new_array_type`, and struct types are keyed on their + /// field types, so without this two equal byte runs would yield two distinct anonymous structs. + pub byte_array_types: RefCell, u64), Type<'gcc>>>, /// Cache instances of monomorphic and polymorphic items pub instances: RefCell, LValue<'gcc>>>, @@ -126,8 +142,14 @@ pub struct CodegenCx<'gcc, 'tcx> { pub pointee_infos: RefCell, Size), Option>>, + /// Blocks that are cleanup landing pads, so `invoke` can tell an unwind + /// edge into a cleanup from a catch/terminate. #[cfg(feature = "master")] - pub cleanup_blocks: RefCell>>, + pub landing_pads: RefCell>>, + /// Cleanup regions to be filled in once the function is fully codegened + /// (done in `populate_cleanup_regions`). + #[cfg(feature = "master")] + pub pending_cleanups: RefCell>>, /// The alignment of a u128/i128 type. // We cache this, since it is needed for alignment checks during loads. pub int128_align: Align, @@ -226,12 +248,7 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { let isize_type = usize_type; let bool_type = context.new_type::(); - let mut functions = FxHashMap::default(); - let builtins = ["abort"]; - - for builtin in builtins.iter() { - functions.insert(builtin.to_string(), context.get_builtin_function(builtin)); - } + let functions = FxHashMap::default(); let mut cx = Self { int128_align: tcx @@ -298,6 +315,7 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { types: Default::default(), tcx, struct_types: Default::default(), + byte_array_types: Default::default(), local_gen_sym_counter: Cell::new(0), global_gen_sym_counter: Cell::new(0), eh_personality: Cell::new(None), @@ -305,13 +323,43 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { rust_try_fn: Cell::new(None), pointee_infos: Default::default(), #[cfg(feature = "master")] - cleanup_blocks: Default::default(), + landing_pads: Default::default(), + #[cfg(feature = "master")] + pending_cleanups: Default::default(), }; // FIXME(antoyo): instead of doing this, add SsizeT to libgccjit. cx.isize_type = usize_type.to_signed(&cx); cx } + /// Fill in the member blocks of every pending cleanup region. + /// + /// Clone all blocks reachable from a cleanup block into the cleanup region. + #[cfg(feature = "master")] + pub fn populate_cleanup_regions(&self) { + let pending = std::mem::take(&mut *self.pending_cleanups.borrow_mut()); + + for cleanup in pending { + // The landing pad is the region's entry, so it must come first. + let mut blocks = vec![]; + let mut visited = FxHashSet::default(); + let mut stack = vec![cleanup.landing_pad]; + while let Some(block) = stack.pop() { + if !visited.insert(block) { + continue; + } + blocks.push(block); + stack.extend(block.get_successors()); + } + + for clone in gccjit::clone_blocks(&blocks) { + cleanup.region.add_block(clone); + } + } + + self.landing_pads.borrow_mut().clear(); + } + pub fn rvalue_as_function(&self, value: RValue<'gcc>) -> Function<'gcc> { let function: Function<'gcc> = unsafe { std::mem::transmute(value) }; debug_assert!( diff --git a/compiler/rustc_codegen_gcc/src/declare.rs b/compiler/rustc_codegen_gcc/src/declare.rs index 4174eebcf7b02..32bb7c3aa349e 100644 --- a/compiler/rustc_codegen_gcc/src/declare.rs +++ b/compiler/rustc_codegen_gcc/src/declare.rs @@ -1,12 +1,12 @@ #[cfg(feature = "master")] -use gccjit::{FnAttribute, ToRValue}; +use gccjit::{FnAttribute, ToRValue, VarAttribute}; use gccjit::{Function, FunctionType, GlobalKind, LValue, RValue, Type}; use rustc_codegen_ssa::traits::BaseTypeCodegenMethods; use rustc_middle::ty::Ty; use rustc_span::Symbol; use rustc_target::callconv::FnAbi; -use crate::abi::{FnAbiGcc, FnAbiGccExt}; +use crate::abi::FnAbiGccExt; use crate::context::CodegenCx; impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { @@ -14,6 +14,7 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { &self, name: &str, ty: Type<'gcc>, + global_kind: GlobalKind, is_tls: bool, link_section: Option, ) -> LValue<'gcc> { @@ -24,11 +25,14 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { global.set_tls_model(self.tls_model); } if let Some(link_section) = link_section { + #[cfg(feature = "master")] + global.add_attribute(VarAttribute::Section(link_section.as_str())); + #[cfg(not(feature = "master"))] global.set_link_section(link_section.as_str()); } global } else { - self.declare_global(name, ty, GlobalKind::Exported, is_tls, link_section) + self.declare_global(name, ty, global_kind, is_tls, link_section) } } @@ -73,6 +77,9 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { global.set_tls_model(self.tls_model); } if let Some(link_section) = link_section { + #[cfg(feature = "master")] + global.add_attribute(VarAttribute::Section(link_section.as_str())); + #[cfg(not(feature = "master"))] global.set_link_section(link_section.as_str()); } let global_address = global.get_address(None); @@ -110,22 +117,22 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { } pub fn declare_fn(&self, name: &str, fn_abi: &FnAbi<'tcx, Ty<'tcx>>) -> Function<'gcc> { - let FnAbiGcc { - return_type, - arguments_type, - is_c_variadic, - on_stack_param_indices, - #[cfg(feature = "master")] - fn_attributes, - } = fn_abi.gcc_type(self); + let fn_abi_gcc = fn_abi.gcc_type(self); #[cfg(feature = "master")] let conv = fn_abi.gcc_cconv(self); #[cfg(not(feature = "master"))] let conv = None; - let func = declare_raw_fn(self, name, conv, return_type, &arguments_type, is_c_variadic); - self.on_stack_function_params.borrow_mut().insert(func, on_stack_param_indices); + let func = declare_raw_fn( + self, + name, + conv, + fn_abi_gcc.return_type, + &fn_abi_gcc.arguments_type, + fn_abi_gcc.is_c_variadic, + ); + self.on_stack_function_params.borrow_mut().insert(func, fn_abi_gcc.on_stack_param_indices); #[cfg(feature = "master")] - for fn_attr in fn_attributes { + for fn_attr in fn_abi_gcc.fn_attributes { func.add_attribute(fn_attr); } func @@ -135,10 +142,11 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { &self, name: &str, ty: Type<'gcc>, + global_kind: GlobalKind, is_tls: bool, link_section: Option, ) -> LValue<'gcc> { - self.get_or_insert_global(name, ty, is_tls, link_section) + self.get_or_insert_global(name, ty, global_kind, is_tls, link_section) } pub fn get_declared_value(&self, name: &str) -> Option> { diff --git a/compiler/rustc_codegen_gcc/src/diagnostics.rs b/compiler/rustc_codegen_gcc/src/diagnostics.rs index de633d3bdde79..67723ebd2f30b 100644 --- a/compiler/rustc_codegen_gcc/src/diagnostics.rs +++ b/compiler/rustc_codegen_gcc/src/diagnostics.rs @@ -20,10 +20,6 @@ pub(crate) struct LtoBitcodeFromRlib { pub gcc_err: String, } -#[derive(Diagnostic)] -#[diag("explicit tail calls with the 'become' keyword are not implemented in the GCC backend")] -pub(crate) struct ExplicitTailCallsUnsupported; - #[derive(Diagnostic)] #[diag("asm contains a NUL byte")] pub(crate) struct NulBytesInAsm { diff --git a/compiler/rustc_codegen_gcc/src/gcc_util.rs b/compiler/rustc_codegen_gcc/src/gcc_util.rs index a95b4da28eb63..fe26e3d8fe5e6 100644 --- a/compiler/rustc_codegen_gcc/src/gcc_util.rs +++ b/compiler/rustc_codegen_gcc/src/gcc_util.rs @@ -1,10 +1,15 @@ -#[cfg(feature = "master")] +use std::borrow::Cow; +use std::collections::HashSet; +use std::env; + use gccjit::Context; +#[cfg(feature = "master")] +use gccjit::Version; use rustc_codegen_ssa::target_features; use rustc_data_structures::smallvec::{SmallVec, smallvec}; use rustc_session::Session; use rustc_session::config::NATIVE_CPU; -use rustc_target::spec::Arch; +use rustc_target::spec::{Arch, RelocModel, StackProbeType, StackProtector}; fn gcc_features_by_flags(sess: &Session, features: &mut Vec) { target_features::retpoline_features_by_flags(sess, features); @@ -110,29 +115,180 @@ pub fn to_gcc_features<'a>(sess: &Session, s: &'a str) -> SmallVec<[&'a str; 2]> fn arch_to_gcc(name: &str) -> &str { match name { "M68000" => "68000", + "M68010" => "68010", "M68020" => "68020", + "M68030" => "68030", + "M68040" => "68040", + "M68060" => "68060", _ => name, } } -fn handle_native(name: &str) -> &str { +fn handle_native(name: &str) -> Cow<'_, str> { if name != NATIVE_CPU { - return arch_to_gcc(name); + return arch_to_gcc(name).into(); } #[cfg(feature = "master")] { // Get the native arch. let context = Context::default(); - context.get_target_info().arch().unwrap().to_str().unwrap() + Cow::Owned(context.get_target_info().arch().to_str().unwrap().to_string()) } #[cfg(not(feature = "master"))] unimplemented!(); } -pub fn target_cpu(sess: &Session) -> &str { +pub fn target_cpu(sess: &Session) -> Cow<'_, str> { match sess.opts.cg.target_cpu { Some(ref name) => handle_native(name), None => handle_native(sess.target.cpu.as_ref()), } } + +pub fn new_context<'gcc>(sess: &Session) -> Context<'gcc> { + let context = Context::default(); + if matches!(sess.target.arch, Arch::X86 | Arch::X86_64) { + context.add_command_line_option("-masm=intel"); + } + #[cfg(feature = "master")] + { + context.set_special_chars_allowed_in_func_names("$.*"); + let version = Version::get(); + let version = format!("{}.{}.{}", version.major, version.minor, version.patch); + context.set_output_ident(&format!( + "rustc version {} with libgccjit {}", + rustc_interface::util::rustc_version_str().unwrap_or("unknown version"), + version, + )); + } + if !sess.must_emit_unwind_tables() { + context.add_command_line_option("-fno-asynchronous-unwind-tables"); + } + + if sess.panic_strategy().unwinds() { + context.add_command_line_option("-fexceptions"); + context.add_driver_option("-fexceptions"); + } + + let disabled_features: HashSet<_> = sess + .opts + .cg + .target_feature + .split(',') + .filter(|feature| feature.starts_with('-')) + .map(|string| &string[1..]) + .collect(); + + if !disabled_features.contains("avx") && sess.target.arch == Arch::X86_64 { + // NOTE: we always enable AVX because the equivalent of llvm.x86.sse2.cmp.pd in GCC for + // SSE2 is multiple builtins, so we use the AVX __builtin_ia32_cmppd instead. + // FIXME(antoyo): use the proper builtins for llvm.x86.sse2.cmp.pd and similar. + context.add_command_line_option("-mavx"); + } + + for arg in &sess.opts.cg.llvm_args { + context.add_command_line_option(arg); + } + // NOTE: This is needed to compile the file src/intrinsic/archs.rs during a bootstrap of rustc. + context.add_command_line_option("-fno-var-tracking-assignments"); + // NOTE: an optimization (https://github.com/rust-lang/rustc_codegen_gcc/issues/53). + context.add_command_line_option("-fno-semantic-interposition"); + // NOTE: Rust relies on LLVM not doing TBAA (https://github.com/rust-lang/unsafe-code-guidelines/issues/292). + context.add_command_line_option("-fno-strict-aliasing"); + // NOTE: Rust relies on LLVM doing wrapping on overflow. + context.add_command_line_option("-fwrapv"); + // NOTE: This is needed to hide a warning caused by the alignment fix on byval arguments. + context.add_command_line_option("-Wno-psabi"); + + if let Some(model) = sess.code_model() { + use rustc_target::spec::CodeModel; + + context.add_command_line_option(match model { + CodeModel::Tiny => "-mcmodel=tiny", + CodeModel::Small => "-mcmodel=small", + CodeModel::Kernel => "-mcmodel=kernel", + CodeModel::Medium => "-mcmodel=medium", + CodeModel::Large => "-mcmodel=large", + }); + } + + match sess.stack_protector() { + StackProtector::All => context.add_command_line_option("-fstack-protector-all"), + StackProtector::Strong => context.add_command_line_option("-fstack-protector-strong"), + StackProtector::Basic => context.add_command_line_option("-fstack-protector"), + StackProtector::None => (), + } + + match sess.target.stack_probes { + StackProbeType::None => (), + StackProbeType::Inline | StackProbeType::InlineOrCall { .. } => { + context.add_command_line_option("-fstack-clash-protection") + } + // FIXME(antoyo): We should define the stack probe symbol to be __rust_probestack, but it seems GCC cannot do that. + StackProbeType::Call => (), + }; + + add_pic_option(&context, sess.relocation_model()); + + let target_cpu = target_cpu(sess); + if target_cpu != "generic" { + context.add_command_line_option(format!("-march={}", target_cpu)); + } + + if sess.opts.unstable_opts.function_sections.unwrap_or(sess.target.function_sections) { + context.add_command_line_option("-ffunction-sections"); + context.add_command_line_option("-fdata-sections"); + } + + if env::var("CG_GCCJIT_DUMP_RTL").as_deref() == Ok("1") { + context.add_command_line_option("-fdump-rtl-vregs"); + } + if env::var("CG_GCCJIT_DUMP_RTL_ALL").as_deref() == Ok("1") { + context.add_command_line_option("-fdump-rtl-all"); + } + if env::var("CG_GCCJIT_DUMP_TREE_ALL").as_deref() == Ok("1") { + context.add_command_line_option("-fdump-tree-all-eh"); + } + if env::var("CG_GCCJIT_DUMP_IPA_ALL").as_deref() == Ok("1") { + context.add_command_line_option("-fdump-ipa-all-eh"); + } + if env::var("CG_GCCJIT_DUMP_CODE").as_deref() == Ok("1") { + context.set_dump_code_on_compile(true); + } + if env::var("CG_GCCJIT_DUMP_GIMPLE").as_deref() == Ok("1") { + context.set_dump_initial_gimple(true); + } + if env::var("CG_GCCJIT_DUMP_EVERYTHING").as_deref() == Ok("1") { + context.set_dump_everything(true); + } + if env::var("CG_GCCJIT_KEEP_INTERMEDIATES").as_deref() == Ok("1") { + context.set_keep_intermediates(true); + } + if env::var("CG_GCCJIT_VERBOSE").as_deref() == Ok("1") { + context.add_driver_option("-v"); + } + + context +} + +pub fn add_pic_option<'gcc>(context: &Context<'gcc>, relocation_model: RelocModel) { + match relocation_model { + rustc_target::spec::RelocModel::Static => { + context.add_command_line_option("-fno-pie"); + context.add_driver_option("-fno-pie"); + } + rustc_target::spec::RelocModel::Pic => { + context.add_command_line_option("-fPIC"); + // NOTE: we use both add_command_line_option and add_driver_option because the usage in + // base (compile_codegen_unit) requires add_command_line_option while the usage + // in the back::write module (codegen) requires add_driver_option. + context.add_driver_option("-fPIC"); + } + rustc_target::spec::RelocModel::Pie => { + context.add_command_line_option("-fPIE"); + context.add_driver_option("-fPIE"); + } + model => eprintln!("Unsupported relocation model: {:?}", model), + } +} diff --git a/compiler/rustc_codegen_gcc/src/int.rs b/compiler/rustc_codegen_gcc/src/int.rs index dfae4eceebe44..4e4b911666143 100644 --- a/compiler/rustc_codegen_gcc/src/int.rs +++ b/compiler/rustc_codegen_gcc/src/int.rs @@ -21,12 +21,12 @@ use crate::context::CodegenCx; impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { pub fn gcc_urem(&self, a: RValue<'gcc>, b: RValue<'gcc>) -> RValue<'gcc> { // 128-bit unsigned %: __umodti3 - self.multiplicative_operation(BinaryOp::Modulo, "mod", false, a, b) + self.division_operation(BinaryOp::Modulo, "mod", false, a, b) } pub fn gcc_srem(&self, a: RValue<'gcc>, b: RValue<'gcc>) -> RValue<'gcc> { // 128-bit signed %: __modti3 - self.multiplicative_operation(BinaryOp::Modulo, "mod", true, a, b) + self.division_operation(BinaryOp::Modulo, "mod", true, a, b) } pub fn gcc_not(&self, a: RValue<'gcc>) -> RValue<'gcc> { @@ -178,6 +178,9 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { } else { debug_assert!(a_type.dyncast_array().is_some()); debug_assert!(b_type.dyncast_array().is_some()); + if a_type != b_type { + b = self.gcc_int_cast(b, a_type); + } let signed = a_type.is_compatible_with(self.i128_type); let func_name = match (operation, signed) { (BinaryOp::Plus, true) => "__rust_i128_add", @@ -187,7 +190,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { _ => unreachable!("unexpected additive operation {:?}", operation), }; let param_a = self.context.new_parameter(self.location, a_type, "a"); - let param_b = self.context.new_parameter(self.location, b_type, "b"); + let param_b = self.context.new_parameter(self.location, a_type, "b"); let func = self.context.new_function( self.location, FunctionType::Extern, @@ -212,6 +215,27 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { self.additive_operation(BinaryOp::Minus, a, b) } + fn division_operation( + &self, + operation: BinaryOp, + operation_name: &str, + signed: bool, + mut a: RValue<'gcc>, + mut b: RValue<'gcc>, + ) -> RValue<'gcc> { + let a_type = a.get_type(); + if self.is_native_int_type(a_type) && self.is_native_int_type(b.get_type()) { + let typ = if signed { a_type.to_signed(self.cx) } else { a_type.to_unsigned(self.cx) }; + if !typ.is_compatible_with(a_type) { + a = self.context.new_cast(self.location, a, typ); + } + if !typ.is_compatible_with(b.get_type()) { + b = self.context.new_cast(self.location, b, typ); + } + } + self.multiplicative_operation(operation, operation_name, signed, a, b) + } + fn multiplicative_operation( &self, operation: BinaryOp, @@ -238,10 +262,13 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { } else { debug_assert!(a_type.dyncast_array().is_some()); debug_assert!(b_type.dyncast_array().is_some()); + if a_type != b_type { + b = self.gcc_int_cast(b, a_type); + } let sign = if signed { "" } else { "u" }; let func_name = format!("__{}{}ti3", sign, operation_name); let param_a = self.context.new_parameter(self.location, a_type, "a"); - let param_b = self.context.new_parameter(self.location, b_type, "b"); + let param_b = self.context.new_parameter(self.location, a_type, "b"); let func = self.context.new_function( self.location, FunctionType::Extern, @@ -255,15 +282,13 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { } pub fn gcc_sdiv(&self, a: RValue<'gcc>, b: RValue<'gcc>) -> RValue<'gcc> { - // FIXME(antoyo): check if the types are signed? // 128-bit, signed: __divti3 - // FIXME(antoyo): convert the arguments to signed? - self.multiplicative_operation(BinaryOp::Divide, "div", true, a, b) + self.division_operation(BinaryOp::Divide, "div", true, a, b) } pub fn gcc_udiv(&self, a: RValue<'gcc>, b: RValue<'gcc>) -> RValue<'gcc> { // 128-bit, unsigned: __udivti3 - self.multiplicative_operation(BinaryOp::Divide, "div", false, a, b) + self.division_operation(BinaryOp::Divide, "div", false, a, b) } pub fn gcc_checked_binop( @@ -432,7 +457,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { if self.is_non_native_int_type(a_type) || self.is_non_native_int_type(b_type) { // This algorithm is based on compiler-rt's __cmpti2: // https://github.com/llvm-mirror/compiler-rt/blob/f0745e8476f069296a7c71accedd061dce4cdf79/lib/builtins/cmpti2.c#L21 - let result = self.current_func().new_local(self.location, self.int_type, "icmp_result"); + let result = self.new_temp(self.current_func(), self.location, self.int_type); let block1 = self.current_func().new_block("block1"); let block2 = self.current_func().new_block("block2"); let block3 = self.current_func().new_block("block3"); @@ -462,9 +487,18 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { lhs_high = self.context.new_cast(self.location, lhs_high, unsigned_type); rhs_high = self.context.new_cast(self.location, rhs_high, unsigned_type); } - // FIXME(antoyo): we probably need to handle signed comparison for unsigned - // integers. - _ => (), + IntPredicate::IntSGT + | IntPredicate::IntSGE + | IntPredicate::IntSLT + | IntPredicate::IntSLE => { + let signed_type = native_int_type.to_signed(self.cx); + lhs_high = self.context.new_cast(self.location, lhs_high, signed_type); + rhs_high = self.context.new_cast(self.location, rhs_high, signed_type); + } + IntPredicate::IntEQ | IntPredicate::IntNE => { + lhs_high = self.context.new_cast(self.location, lhs_high, unsigned_type); + rhs_high = self.context.new_cast(self.location, rhs_high, unsigned_type); + } } let condition = self.context.new_comparison( @@ -602,9 +636,17 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { rhs = self.context.new_cast(self.location, rhs, unsigned_type); } } - // FIXME(antoyo): we probably need to handle signed comparison for unsigned - // integers. - _ => (), + IntPredicate::IntSGT + | IntPredicate::IntSGE + | IntPredicate::IntSLT + | IntPredicate::IntSLE => { + if !a_type.is_vector() { + let signed_type = a_type.to_signed(self.cx); + lhs = self.context.new_cast(self.location, lhs, signed_type); + rhs = self.context.new_cast(self.location, rhs, signed_type); + } + } + IntPredicate::IntEQ | IntPredicate::IntNE => (), } self.context.new_comparison(self.location, op.to_gcc_comparison(), lhs, rhs) } @@ -623,6 +665,9 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { } a ^ b } else { + if a_type != b_type { + b = self.gcc_int_cast(b, a_type); + } self.concat_low_high_rvalues( a_type, self.low(a) ^ self.low(b), @@ -832,6 +877,9 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { !a_native && !b_native, "both types should either be native or non-native for or operation" ); + if a_type != b_type { + b = self.gcc_int_cast(b, a_type); + } let native_int_type = a_type.dyncast_array().expect("get element type"); self.concat_low_high_rvalues( a_type, @@ -862,7 +910,7 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { self.bitwise_operation(BinaryOp::BitwiseOr, a, b, loc) } - // FIXME(antoyo): can we use https://github.com/rust-lang/compiler-builtins/blob/master/src/int/mod.rs#L379 instead? + // FIXME(antoyo): can we use https://github.com/rust-lang/compiler-builtins/blob/1a99c2aa295bb2d507fa0e67a3b5eef64fba92a0/libm/src/math/support/int_traits.rs#L485 instead? pub fn gcc_int_cast(&self, value: RValue<'gcc>, dest_typ: Type<'gcc>) -> RValue<'gcc> { let value_type = value.get_type(); if self.is_native_int_type_or_bool(dest_typ) && self.is_native_int_type_or_bool(value_type) diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/archs.rs b/compiler/rustc_codegen_gcc/src/intrinsic/archs.rs index 3c1698df6dec2..1856c2468616d 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/archs.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/archs.rs @@ -24,6 +24,7 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "gcsss" => "__builtin_arm_gcsss", "isb" => "__builtin_arm_isb", "prefetch" => "__builtin_arm_prefetch", + "prefetch.ir" => "__builtin_arm_prefetch_ir", "range.prefetch" => "__builtin_arm_range_prefetch", "sme.in.streaming.mode" => "__builtin_arm_in_streaming_mode", "sve.aesd" => "__builtin_sve_svaesd_u8", @@ -53,6 +54,7 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "alignbyte" => "__builtin_amdgcn_alignbyte", "ashr.pk.i8.i32" => "__builtin_amdgcn_ashr_pk_i8_i32", "ashr.pk.u8.i32" => "__builtin_amdgcn_ashr_pk_u8_i32", + "asyncmark" => "__builtin_amdgcn_asyncmark", "buffer.wbinvl1" => "__builtin_amdgcn_buffer_wbinvl1", "buffer.wbinvl1.sc" => "__builtin_amdgcn_buffer_wbinvl1_sc", "buffer.wbinvl1.vol" => "__builtin_amdgcn_buffer_wbinvl1_vol", @@ -270,6 +272,7 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "fdot2c.f32.bf16" => "__builtin_amdgcn_fdot2c_f32_bf16", "flat.prefetch" => "__builtin_amdgcn_flat_prefetch", "fmul.legacy" => "__builtin_amdgcn_fmul_legacy", + "global.load.async.lds" => "__builtin_amdgcn_global_load_async_lds", "global.load.async.to.lds.b128" => { "__builtin_amdgcn_global_load_async_to_lds_b128" } @@ -361,11 +364,7 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "perm.pk16.b4.u4" => "__builtin_amdgcn_perm_pk16_b4_u4", "perm.pk16.b6.u4" => "__builtin_amdgcn_perm_pk16_b6_u4", "perm.pk16.b8.u4" => "__builtin_amdgcn_perm_pk16_b8_u4", - "permlane.bcast" => "__builtin_amdgcn_permlane_bcast", - "permlane.down" => "__builtin_amdgcn_permlane_down", "permlane.idx.gen" => "__builtin_amdgcn_permlane_idx_gen", - "permlane.up" => "__builtin_amdgcn_permlane_up", - "permlane.xor" => "__builtin_amdgcn_permlane_xor", "permlane16.var" => "__builtin_amdgcn_permlane16_var", "permlanex16.var" => "__builtin_amdgcn_permlanex16_var", "pk.add.max.i16" => "__builtin_amdgcn_pk_add_max_i16", @@ -375,6 +374,9 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "prng.b32" => "__builtin_amdgcn_prng_b32", "qsad.pk.u16.u8" => "__builtin_amdgcn_qsad_pk_u16_u8", "queue.ptr" => "__builtin_amdgcn_queue_ptr", + "raw.ptr.buffer.load.async.lds" => { + "__builtin_amdgcn_raw_ptr_buffer_load_async_lds" + } "raw.ptr.buffer.load.lds" => "__builtin_amdgcn_raw_ptr_buffer_load_lds", "rcp.legacy" => "__builtin_amdgcn_rcp_legacy", "rsq.legacy" => "__builtin_amdgcn_rsq_legacy", @@ -386,6 +388,7 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "s.barrier.signal.isfirst" => "__builtin_amdgcn_s_barrier_signal_isfirst", "s.barrier.signal.var" => "__builtin_amdgcn_s_barrier_signal_var", "s.barrier.wait" => "__builtin_amdgcn_s_barrier_wait", + "s.bitreplicate" => "__builtin_amdgcn_s_bitreplicate", "s.buffer.prefetch.data" => "__builtin_amdgcn_s_buffer_prefetch_data", "s.cluster.barrier" => "__builtin_amdgcn_s_cluster_barrier", "s.dcache.inv" => "__builtin_amdgcn_s_dcache_inv", @@ -412,6 +415,7 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "s.ttracedata" => "__builtin_amdgcn_s_ttracedata", "s.ttracedata.imm" => "__builtin_amdgcn_s_ttracedata_imm", "s.wait.asynccnt" => "__builtin_amdgcn_s_wait_asynccnt", + "s.wait.event" => "__builtin_amdgcn_s_wait_event", "s.wait.event.export.ready" => "__builtin_amdgcn_s_wait_event_export_ready", "s.wait.tensorcnt" => "__builtin_amdgcn_s_wait_tensorcnt", "s.waitcnt" => "__builtin_amdgcn_s_waitcnt", @@ -462,16 +466,18 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "smfmac.i32.16x16x64.i8" => "__builtin_amdgcn_smfmac_i32_16x16x64_i8", "smfmac.i32.32x32x32.i8" => "__builtin_amdgcn_smfmac_i32_32x32x32_i8", "smfmac.i32.32x32x64.i8" => "__builtin_amdgcn_smfmac_i32_32x32x64_i8", + "struct.ptr.buffer.load.async.lds" => { + "__builtin_amdgcn_struct_ptr_buffer_load_async_lds" + } "struct.ptr.buffer.load.lds" => "__builtin_amdgcn_struct_ptr_buffer_load_lds", "sudot4" => "__builtin_amdgcn_sudot4", "sudot8" => "__builtin_amdgcn_sudot8", "tensor.load.to.lds" => "__builtin_amdgcn_tensor_load_to_lds", - "tensor.load.to.lds.d2" => "__builtin_amdgcn_tensor_load_to_lds_d2", "tensor.store.from.lds" => "__builtin_amdgcn_tensor_store_from_lds", - "tensor.store.from.lds.d2" => "__builtin_amdgcn_tensor_store_from_lds_d2", "udot2" => "__builtin_amdgcn_udot2", "udot4" => "__builtin_amdgcn_udot4", "udot8" => "__builtin_amdgcn_udot8", + "wait.asyncmark" => "__builtin_amdgcn_wait_asyncmark", "wave.barrier" => "__builtin_amdgcn_wave_barrier", "wavefrontsize" => "__builtin_amdgcn_wavefrontsize", "workgroup.id.x" => "__builtin_amdgcn_workgroup_id_x", @@ -4844,7 +4850,11 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "add.rn.f" => "__nvvm_add_rn_f", "add.rn.ftz.f" => "__nvvm_add_rn_ftz_f", "add.rn.ftz.sat.f" => "__nvvm_add_rn_ftz_sat_f", + "add.rn.ftz.sat.f16" => "__nvvm_add_rn_ftz_sat_f16", + "add.rn.ftz.sat.v2f16" => "__nvvm_add_rn_ftz_sat_v2f16", "add.rn.sat.f" => "__nvvm_add_rn_sat_f", + "add.rn.sat.f16" => "__nvvm_add_rn_sat_f16", + "add.rn.sat.v2f16" => "__nvvm_add_rn_sat_v2f16", "add.rp.d" => "__nvvm_add_rp_d", "add.rp.f" => "__nvvm_add_rp_f", "add.rp.ftz.f" => "__nvvm_add_rp_ftz_f", @@ -5063,18 +5073,10 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "fma.rn.bf16x2" => "__nvvm_fma_rn_bf16x2", "fma.rn.d" => "__nvvm_fma_rn_d", "fma.rn.f" => "__nvvm_fma_rn_f", - "fma.rn.ftz.bf16" => "__nvvm_fma_rn_ftz_bf16", - "fma.rn.ftz.bf16x2" => "__nvvm_fma_rn_ftz_bf16x2", "fma.rn.ftz.f" => "__nvvm_fma_rn_ftz_f", - "fma.rn.ftz.relu.bf16" => "__nvvm_fma_rn_ftz_relu_bf16", - "fma.rn.ftz.relu.bf16x2" => "__nvvm_fma_rn_ftz_relu_bf16x2", - "fma.rn.ftz.sat.bf16" => "__nvvm_fma_rn_ftz_sat_bf16", - "fma.rn.ftz.sat.bf16x2" => "__nvvm_fma_rn_ftz_sat_bf16x2", "fma.rn.ftz.sat.f" => "__nvvm_fma_rn_ftz_sat_f", "fma.rn.relu.bf16" => "__nvvm_fma_rn_relu_bf16", "fma.rn.relu.bf16x2" => "__nvvm_fma_rn_relu_bf16x2", - "fma.rn.sat.bf16" => "__nvvm_fma_rn_sat_bf16", - "fma.rn.sat.bf16x2" => "__nvvm_fma_rn_sat_bf16x2", "fma.rn.sat.f" => "__nvvm_fma_rn_sat_f", "fma.rp.d" => "__nvvm_fma_rp_d", "fma.rp.f" => "__nvvm_fma_rp_f", @@ -5195,6 +5197,10 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "mul.rn.d" => "__nvvm_mul_rn_d", "mul.rn.f" => "__nvvm_mul_rn_f", "mul.rn.ftz.f" => "__nvvm_mul_rn_ftz_f", + "mul.rn.ftz.sat.f16" => "__nvvm_mul_rn_ftz_sat_f16", + "mul.rn.ftz.sat.v2f16" => "__nvvm_mul_rn_ftz_sat_v2f16", + "mul.rn.sat.f16" => "__nvvm_mul_rn_sat_f16", + "mul.rn.sat.v2f16" => "__nvvm_mul_rn_sat_v2f16", "mul.rp.d" => "__nvvm_mul_rp_d", "mul.rp.f" => "__nvvm_mul_rp_f", "mul.rp.ftz.f" => "__nvvm_mul_rp_ftz_f", @@ -5827,8 +5833,10 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "altivec.vmuleuh" => "__builtin_altivec_vmuleuh", "altivec.vmuleuw" => "__builtin_altivec_vmuleuw", "altivec.vmulhsd" => "__builtin_altivec_vmulhsd", + "altivec.vmulhsh" => "__builtin_altivec_vmulhsh", "altivec.vmulhsw" => "__builtin_altivec_vmulhsw", "altivec.vmulhud" => "__builtin_altivec_vmulhud", + "altivec.vmulhuh" => "__builtin_altivec_vmulhuh", "altivec.vmulhuw" => "__builtin_altivec_vmulhuw", "altivec.vmulosb" => "__builtin_altivec_vmulosb", "altivec.vmulosd" => "__builtin_altivec_vmulosd", @@ -5912,22 +5920,45 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "altivec.vsum4shs" => "__builtin_altivec_vsum4shs", "altivec.vsum4ubs" => "__builtin_altivec_vsum4ubs", "altivec.vsumsws" => "__builtin_altivec_vsumsws", + "altivec.vucmprhb" => "__builtin_altivec_vucmprhb", + "altivec.vucmprhh" => "__builtin_altivec_vucmprhh", + "altivec.vucmprhn" => "__builtin_altivec_vucmprhn", + "altivec.vucmprlb" => "__builtin_altivec_vucmprlb", + "altivec.vucmprlh" => "__builtin_altivec_vucmprlh", + "altivec.vucmprln" => "__builtin_altivec_vucmprln", "altivec.vupkhpx" => "__builtin_altivec_vupkhpx", "altivec.vupkhsb" => "__builtin_altivec_vupkhsb", "altivec.vupkhsh" => "__builtin_altivec_vupkhsh", + "altivec.vupkhsntob" => "__builtin_altivec_vupkhsntob", "altivec.vupkhsw" => "__builtin_altivec_vupkhsw", + "altivec.vupkint4tobf16" => "__builtin_altivec_vupkint4tobf16", + "altivec.vupkint4tofp32" => "__builtin_altivec_vupkint4tofp32", + "altivec.vupkint8tobf16" => "__builtin_altivec_vupkint8tobf16", + "altivec.vupkint8tofp32" => "__builtin_altivec_vupkint8tofp32", "altivec.vupklpx" => "__builtin_altivec_vupklpx", "altivec.vupklsb" => "__builtin_altivec_vupklsb", "altivec.vupklsh" => "__builtin_altivec_vupklsh", + "altivec.vupklsntob" => "__builtin_altivec_vupklsntob", "altivec.vupklsw" => "__builtin_altivec_vupklsw", "amo.ldat" => "__builtin_amo_ldat", + "amo.ldat.cond" => "__builtin_amo_ldat_cond", + "amo.ldat.csne" => "__builtin_amo_ldat_csne", "amo.lwat" => "__builtin_amo_lwat", + "amo.lwat.cond" => "__builtin_amo_lwat_cond", + "amo.lwat.csne" => "__builtin_amo_lwat_csne", + "amo.stdat" => "__builtin_amo_stdat", + "amo.stwat" => "__builtin_amo_stwat", "bcdadd" => "__builtin_ppc_bcdadd", "bcdadd.p" => "__builtin_ppc_bcdadd_p", "bcdcopysign" => "__builtin_ppc_bcdcopysign", "bcdsetsign" => "__builtin_ppc_bcdsetsign", + "bcdshift" => "__builtin_ppc_bcdshift", + "bcdshiftround" => "__builtin_ppc_bcdshiftround", "bcdsub" => "__builtin_ppc_bcdsub", "bcdsub.p" => "__builtin_ppc_bcdsub_p", + "bcdtruncate" => "__builtin_ppc_bcdtruncate", + "bcdunsignedshift" => "__builtin_ppc_bcdunsignedshift", + "bcdunsignedtruncate" => "__builtin_ppc_bcdunsignedtruncate", "bpermd" => "__builtin_bpermd", "cbcdtd" => "__builtin_cbcdtd", "cbcdtdd" => "__builtin_ppc_cbcdtd", @@ -6126,6 +6157,27 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "vsx.xxinsertw" => "__builtin_vsx_xxinsertw", "vsx.xxleqv" => "__builtin_vsx_xxleqv", "vsx.xxpermx" => "__builtin_vsx_xxpermx", + "xsaddaddsuqm" => "__builtin_xsaddaddsuqm", + "xsaddadduqm" => "__builtin_xsaddadduqm", + "xsaddsubsuqm" => "__builtin_xsaddsubsuqm", + "xsaddsubuqm" => "__builtin_xsaddsubuqm", + "xsmerge2t1uqm" => "__builtin_xsmerge2t1uqm", + "xsmerge2t2uqm" => "__builtin_xsmerge2t2uqm", + "xsmerge2t3uqm" => "__builtin_xsmerge2t3uqm", + "xsmerge3t1uqm" => "__builtin_xsmerge3t1uqm", + "xsrebase2t1uqm" => "__builtin_xsrebase2t1uqm", + "xsrebase2t2uqm" => "__builtin_xsrebase2t2uqm", + "xsrebase2t3uqm" => "__builtin_xsrebase2t3uqm", + "xsrebase2t4uqm" => "__builtin_xsrebase2t4uqm", + "xsrebase3t1uqm" => "__builtin_xsrebase3t1uqm", + "xsrebase3t2uqm" => "__builtin_xsrebase3t2uqm", + "xsrebase3t3uqm" => "__builtin_xsrebase3t3uqm", + "xxmulmul" => "__builtin_xxmulmul", + "xxmulmulhiadd" => "__builtin_xxmulmulhiadd", + "xxmulmulloadd" => "__builtin_xxmulmulloadd", + "xxssumudm" => "__builtin_xxssumudm", + "xxssumudmc" => "__builtin_xxssumudmc", + "xxssumudmcext" => "__builtin_xxssumudmcext", "zoned2packed" => "__builtin_ppc_zoned2packed", _ => unimplemented!("***** unsupported LLVM intrinsic {full_name}"), } @@ -6388,13 +6440,13 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { // spv "group.memory.barrier.with.group.sync" => "__builtin_spirv_group_barrier", "num.subgroups" => "__builtin_spirv_num_subgroups", + "subgroup.ballot" => "__builtin_spirv_subgroup_ballot", "subgroup.id" => "__builtin_spirv_subgroup_id", "subgroup.local.invocation.id" => { "__builtin_spirv_subgroup_local_invocation_id" } "subgroup.max.size" => "__builtin_spirv_subgroup_max_size", "subgroup.size" => "__builtin_spirv_subgroup_size", - "wave.ballot" => "__builtin_spirv_subgroup_ballot", _ => unimplemented!("***** unsupported LLVM intrinsic {full_name}"), } } @@ -8661,10 +8713,6 @@ fn map_arch_intrinsic(full_name: &str) -> &'static str { "bmi.bextr.64" => "__builtin_ia32_bextr_u64", "bmi.bzhi.32" => "__builtin_ia32_bzhi_si", "bmi.bzhi.64" => "__builtin_ia32_bzhi_di", - "bmi.pdep.32" => "__builtin_ia32_pdep_si", - "bmi.pdep.64" => "__builtin_ia32_pdep_di", - "bmi.pext.32" => "__builtin_ia32_pext_si", - "bmi.pext.64" => "__builtin_ia32_pext_di", "cldemote" => "__builtin_ia32_cldemote", "clflushopt" => "__builtin_ia32_clflushopt", "clrssbsy" => "__builtin_ia32_clrssbsy", diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/llvm.rs b/compiler/rustc_codegen_gcc/src/intrinsic/llvm.rs index 41efe3e8209bf..ef381715c1ea2 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/llvm.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/llvm.rs @@ -5,6 +5,7 @@ use rustc_codegen_ssa::traits::BuilderMethods; use crate::builder::Builder; use crate::context::{CodegenCx, new_array_type}; +use crate::type_::{StructAttribute, apply_struct_attributes}; fn encode_key_128_type<'a, 'gcc, 'tcx>( builder: &Builder<'a, 'gcc, 'tcx>, @@ -22,8 +23,7 @@ fn encode_key_128_type<'a, 'gcc, 'tcx>( "EncodeKey128Output", &[field1, field2, field3, field4, field5, field6, field7], ); - #[cfg(feature = "master")] - encode_type.as_type().set_packed(); + apply_struct_attributes(encode_type.as_type(), &[StructAttribute::Packed]); (encode_type.as_type(), field1, field2) } @@ -44,8 +44,7 @@ fn encode_key_256_type<'a, 'gcc, 'tcx>( "EncodeKey256Output", &[field1, field2, field3, field4, field5, field6, field7, field8], ); - #[cfg(feature = "master")] - encode_type.as_type().set_packed(); + apply_struct_attributes(encode_type.as_type(), &[StructAttribute::Packed]); (encode_type.as_type(), field1, field2) } @@ -57,8 +56,7 @@ fn aes_output_type<'a, 'gcc, 'tcx>( let field2 = builder.context.new_field(None, m128i, "field2"); let aes_output_type = builder.context.new_struct_type(None, "AesOutput", &[field1, field2]); let typ = aes_output_type.as_type(); - #[cfg(feature = "master")] - typ.set_packed(); + apply_struct_attributes(typ, &[StructAttribute::Packed]); (typ, field1, field2) } @@ -80,8 +78,7 @@ fn wide_aes_output_type<'a, 'gcc, 'tcx>( "WideAesOutput", &[field1, field2, field3, field4, field5, field6, field7, field8, field9], ); - #[cfg(feature = "master")] - aes_output_type.as_type().set_packed(); + apply_struct_attributes(aes_output_type.as_type(), &[StructAttribute::Packed]); (aes_output_type.as_type(), field1, field2) } @@ -478,6 +475,26 @@ pub fn adjust_intrinsic_arguments<'a, 'b, 'gcc, 'tcx>( new_args.push(variable.get_address(None)); args = new_args.into(); } + "__builtin_ia32_2intersectd128" + | "__builtin_ia32_2intersectq128" + | "__builtin_ia32_2intersectd256" + | "__builtin_ia32_2intersectq256" + | "__builtin_ia32_2intersectd512" + | "__builtin_ia32_2intersectq512" => { + let old_args = args.to_vec(); + let mut new_args = vec![]; + let arg1_type = gcc_func.get_param_type(0); + let first_mask = + builder.current_func().new_local(None, arg1_type, "return_2intersect_arg1"); + let arg2_type = gcc_func.get_param_type(1); + let second_mask = + builder.current_func().new_local(None, arg2_type, "return_2intersect_arg2"); + new_args.push(first_mask.get_address(None)); + new_args.push(second_mask.get_address(None)); + new_args.push(old_args[0]); + new_args.push(old_args[1]); + args = new_args.into(); + } "__builtin_ia32_vpermt2varqi512_mask" | "__builtin_ia32_vpermt2varqi256_mask" | "__builtin_ia32_vpermt2varqi128_mask" @@ -489,6 +506,23 @@ pub fn adjust_intrinsic_arguments<'a, 'b, 'gcc, 'tcx>( let minus_one = builder.context.new_rvalue_from_int(arg4_type, -1); args = vec![new_args[1], new_args[0], new_args[2], minus_one].into(); } + "__builtin_ia32_fpclassph128_mask" + | "__builtin_ia32_fpclassph256_mask" + | "__builtin_ia32_fpclassph512_mask" + | "__builtin_ia32_fpclasspd128_mask" + | "__builtin_ia32_fpclassps128_mask" + | "__builtin_ia32_fpclasspd256_mask" + | "__builtin_ia32_fpclassps256_mask" + | "__builtin_ia32_fpclasspd512_mask" + | "__builtin_ia32_fpclassps512_mask" + | "__builtin_ia32_vpshufbitqmb128_mask" + | "__builtin_ia32_vpshufbitqmb256_mask" + | "__builtin_ia32_vpshufbitqmb512_mask" => { + let new_args = args.to_vec(); + let arg3_type = gcc_func.get_param_type(2); + let minus_one = builder.context.new_rvalue_from_int(arg3_type, -1); + args = vec![new_args[0], new_args[1], minus_one].into(); + } "__builtin_ia32_xrstor" | "__builtin_ia32_xrstor64" | "__builtin_ia32_xsavec" @@ -840,7 +874,7 @@ pub fn adjust_intrinsic_return_value<'a, 'gcc, 'tcx>( "__builtin_ia32_rdrand64_step" => { let random_number = args[0].dereference(None).to_rvalue(); let success_variable = - builder.current_func().new_local(None, return_value.get_type(), "success"); + builder.new_temp(builder.current_func(), None, return_value.get_type()); builder.llbb().add_assignment(None, success_variable, return_value); let field1 = builder.context.new_field(None, random_number.get_type(), "random_number"); @@ -854,6 +888,25 @@ pub fn adjust_intrinsic_return_value<'a, 'gcc, 'tcx>( &[random_number, success_variable.to_rvalue()], ); } + "__builtin_ia32_2intersectd128" + | "__builtin_ia32_2intersectq128" + | "__builtin_ia32_2intersectd256" + | "__builtin_ia32_2intersectq256" + | "__builtin_ia32_2intersectd512" + | "__builtin_ia32_2intersectq512" => { + let first_mask = args[0].dereference(None).to_rvalue(); + let second_mask = args[1].dereference(None).to_rvalue(); + let field1 = builder.context.new_field(None, first_mask.get_type(), "first_mask"); + let field2 = builder.context.new_field(None, second_mask.get_type(), "second_mask"); + let struct_type = + builder.context.new_struct_type(None, "vp2intersect_result", &[field1, field2]); + return_value = builder.context.new_struct_constructor( + None, + struct_type.as_type(), + None, + &[first_mask, second_mask], + ); + } "fma" => { let f16_type = builder.context.new_c_type(CType::Float16); return_value = builder.context.new_cast(None, return_value, f16_type); @@ -1182,6 +1235,9 @@ pub fn intrinsic<'gcc, 'tcx>(name: &str, cx: &CodegenCx<'gcc, 'tcx>) -> Function "llvm.x86.avx512.mask.vpshufbitqmb.512" => "__builtin_ia32_vpshufbitqmb512_mask", "llvm.x86.avx512.mask.vpshufbitqmb.256" => "__builtin_ia32_vpshufbitqmb256_mask", "llvm.x86.avx512.mask.vpshufbitqmb.128" => "__builtin_ia32_vpshufbitqmb128_mask", + "llvm.x86.avx512.vpshufbitqmb.512" => "__builtin_ia32_vpshufbitqmb512_mask", + "llvm.x86.avx512.vpshufbitqmb.256" => "__builtin_ia32_vpshufbitqmb256_mask", + "llvm.x86.avx512.vpshufbitqmb.128" => "__builtin_ia32_vpshufbitqmb128_mask", "llvm.x86.avx512.mask.ucmp.w.512" => "__builtin_ia32_ucmpw512_mask", "llvm.x86.avx512.mask.ucmp.w.256" => "__builtin_ia32_ucmpw256_mask", "llvm.x86.avx512.mask.ucmp.w.128" => "__builtin_ia32_ucmpw128_mask", @@ -1339,11 +1395,20 @@ pub fn intrinsic<'gcc, 'tcx>(name: &str, cx: &CodegenCx<'gcc, 'tcx>) -> Function "llvm.x86.avx512bf16.cvtne2ps2bf16.128" => "__builtin_ia32_cvtne2ps2bf16_v8bf", "llvm.x86.avx512bf16.cvtne2ps2bf16.256" => "__builtin_ia32_cvtne2ps2bf16_v16bf", "llvm.x86.avx512bf16.cvtne2ps2bf16.512" => "__builtin_ia32_cvtne2ps2bf16_v32bf", + "llvm.x86.vcvtneps2bf16128" => "__builtin_ia32_cvtneps2bf16_v4sf", + "llvm.x86.vcvtneps2bf16256" => "__builtin_ia32_cvtneps2bf16_v8sf", + "llvm.x86.avx512bf16.mask.cvtneps2bf16.128" => "__builtin_ia32_cvtneps2bf16_v4sf_mask", "llvm.x86.avx512bf16.cvtneps2bf16.256" => "__builtin_ia32_cvtneps2bf16_v8sf", "llvm.x86.avx512bf16.cvtneps2bf16.512" => "__builtin_ia32_cvtneps2bf16_v16sf", "llvm.x86.avx512bf16.dpbf16ps.128" => "__builtin_ia32_dpbf16ps_v4sf", "llvm.x86.avx512bf16.dpbf16ps.256" => "__builtin_ia32_dpbf16ps_v8sf", "llvm.x86.avx512bf16.dpbf16ps.512" => "__builtin_ia32_dpbf16ps_v16sf", + "llvm.x86.avx512.vp2intersect.d.128" => "__builtin_ia32_2intersectd128", + "llvm.x86.avx512.vp2intersect.q.128" => "__builtin_ia32_2intersectq128", + "llvm.x86.avx512.vp2intersect.d.256" => "__builtin_ia32_2intersectd256", + "llvm.x86.avx512.vp2intersect.q.256" => "__builtin_ia32_2intersectq256", + "llvm.x86.avx512.vp2intersect.d.512" => "__builtin_ia32_2intersectd512", + "llvm.x86.avx512.vp2intersect.q.512" => "__builtin_ia32_2intersectq512", "llvm.x86.pclmulqdq.512" => "__builtin_ia32_vpclmulqdq_v8di", "llvm.x86.pclmulqdq.256" => "__builtin_ia32_vpclmulqdq_v4di", "llvm.x86.avx512.pmulhu.w.512" => "__builtin_ia32_pmulhuw512_mask", @@ -1577,38 +1642,79 @@ pub fn intrinsic<'gcc, 'tcx>(name: &str, cx: &CodegenCx<'gcc, 'tcx>) -> Function "llvm.x86.avx512.uitofp.round.v4f64.v4i64" => "__builtin_ia32_cvtuqq2pd256_mask", "llvm.x86.avx512.uitofp.round.v8f32.v8i64" => "__builtin_ia32_cvtuqq2ps512_mask", "llvm.x86.avx512.uitofp.round.v4f32.v4i64" => "__builtin_ia32_cvtuqq2ps256_mask", + "llvm.x86.avx512fp16.fpclass.ph.128" => "__builtin_ia32_fpclassph128_mask", + "llvm.x86.avx512fp16.mask.cmp.ph.128" => "__builtin_ia32_cmpph128_mask", + "llvm.x86.avx512fp16.fpclass.ph.256" => "__builtin_ia32_fpclassph256_mask", + "llvm.x86.avx512fp16.fpclass.ph.512" => "__builtin_ia32_fpclassph512_mask", + "llvm.x86.avx512fp16.mask.cmp.ph.256" => "__builtin_ia32_cmpph256_mask", + "llvm.x86.avx512fp16.mask.cmp.ph.512" => "__builtin_ia32_cmpph512_mask_round", + "llvm.x86.avx512.fpclass.pd.128" => "__builtin_ia32_fpclasspd128_mask", + "llvm.x86.avx512.fpclass.ps.128" => "__builtin_ia32_fpclassps128_mask", + "llvm.x86.avx512.fpclass.pd.256" => "__builtin_ia32_fpclasspd256_mask", + "llvm.x86.avx512.fpclass.ps.256" => "__builtin_ia32_fpclassps256_mask", + "llvm.x86.avx512.fpclass.pd.512" => "__builtin_ia32_fpclasspd512_mask", + "llvm.x86.avx512.fpclass.ps.512" => "__builtin_ia32_fpclassps512_mask", // FIXME: support the tile builtins: "llvm.x86.ldtilecfg" => "__builtin_trap", "llvm.x86.sttilecfg" => "__builtin_trap", "llvm.x86.tileloadd64" => "__builtin_trap", + "llvm.x86.tileloadd64.internal" => "__builtin_trap", "llvm.x86.tilerelease" => "__builtin_trap", "llvm.x86.tilestored64" => "__builtin_trap", + "llvm.x86.tilestored64.internal" => "__builtin_trap", "llvm.x86.tileloaddrs64" => "__builtin_trap", + "llvm.x86.tileloaddrs64.internal" => "__builtin_trap", "llvm.x86.tileloaddt164" => "__builtin_trap", + "llvm.x86.tileloaddt164.internal" => "__builtin_trap", "llvm.x86.tileloaddrst164" => "__builtin_trap", + "llvm.x86.tileloaddrst164.internal" => "__builtin_trap", "llvm.x86.tilezero" => "__builtin_trap", + "llvm.x86.tilezero.internal" => "__builtin_trap", "llvm.x86.tilemovrow" => "__builtin_trap", + "llvm.x86.tilemovrow.internal" => "__builtin_trap", "llvm.x86.tilemovrowi" => "__builtin_trap", "llvm.x86.tdpbhf8ps" => "__builtin_trap", + "llvm.x86.tdpbhf8ps.internal" => "__builtin_trap", "llvm.x86.tdphbf8ps" => "__builtin_trap", + "llvm.x86.tdphbf8ps.internal" => "__builtin_trap", "llvm.x86.tdpbf8ps" => "__builtin_trap", + "llvm.x86.tdpbf8ps.internal" => "__builtin_trap", "llvm.x86.tdphf8ps" => "__builtin_trap", + "llvm.x86.tdphf8ps.internal" => "__builtin_trap", "llvm.x86.tdpbf16ps" => "__builtin_trap", + "llvm.x86.tdpbf16ps.internal" => "__builtin_trap", "llvm.x86.tdpbssd" => "__builtin_trap", + "llvm.x86.tdpbssd.internal" => "__builtin_trap", "llvm.x86.tdpbsud" => "__builtin_trap", + "llvm.x86.tdpbsud.internal" => "__builtin_trap", "llvm.x86.tdpbusd" => "__builtin_trap", + "llvm.x86.tdpbusd.internal" => "__builtin_trap", "llvm.x86.tdpbuud" => "__builtin_trap", + "llvm.x86.tdpbuud.internal" => "__builtin_trap", "llvm.x86.tdpfp16ps" => "__builtin_trap", + "llvm.x86.tdpfp16ps.internal" => "__builtin_trap", "llvm.x86.tmmultf32ps" => "__builtin_trap", + "llvm.x86.tmmultf32ps.internal" => "__builtin_trap", "llvm.x86.tcvtrowps2phh" => "__builtin_trap", + "llvm.x86.tcvtrowps2phh.internal" => "__builtin_trap", "llvm.x86.tcvtrowps2phl" => "__builtin_trap", + "llvm.x86.tcvtrowps2phl.internal" => "__builtin_trap", "llvm.x86.tcvtrowd2ps" => "__builtin_trap", + "llvm.x86.tcvtrowd2ps.internal" => "__builtin_trap", "llvm.x86.tcvtrowd2psi" => "__builtin_trap", "llvm.x86.tcvtrowps2phhi" => "__builtin_trap", "llvm.x86.tcvtrowps2phli" => "__builtin_trap", + "llvm.x86.tcvtrowps2bf16h" => "__builtin_trap", + "llvm.x86.tcvtrowps2bf16h.internal" => "__builtin_trap", + "llvm.x86.tcvtrowps2bf16hi" => "__builtin_trap", + "llvm.x86.tcvtrowps2bf16l" => "__builtin_trap", + "llvm.x86.tcvtrowps2bf16l.internal" => "__builtin_trap", + "llvm.x86.tcvtrowps2bf16li" => "__builtin_trap", "llvm.x86.tcmmimfp16ps" => "__builtin_trap", + "llvm.x86.tcmmimfp16ps.internal" => "__builtin_trap", "llvm.x86.tcmmrlfp16ps" => "__builtin_trap", + "llvm.x86.tcmmrlfp16ps.internal" => "__builtin_trap", // NOTE: this file is generated by https://github.com/GuillaumeGomez/llvmint/blob/master/generate_list.py _ => map_arch_intrinsic(name), diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs index 5550d22b33aa3..b6015a74d9eb8 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/mod.rs @@ -4,7 +4,7 @@ mod simd; #[cfg(feature = "master")] use std::iter; -use gccjit::{ComparisonOp, Function, FunctionType, RValue, ToRValue, Type, UnaryOp}; +use gccjit::{CType, ComparisonOp, Function, FunctionType, RValue, ToRValue, Type, UnaryOp}; use rustc_abi::{Align, BackendRepr, HasDataLayout, WrappingRange}; use rustc_codegen_ssa::base::wants_msvc_seh; use rustc_codegen_ssa::common::IntPredicate; @@ -25,6 +25,7 @@ use rustc_middle::ty::layout::FnAbiOf; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::{self, Instance, Ty}; use rustc_middle::{bug, span_bug}; +use rustc_session::config::OptLevel; use rustc_span::{Span, Symbol, sym}; use rustc_target::callconv::{ArgAbi, PassMode}; @@ -81,7 +82,6 @@ fn get_simple_intrinsic<'gcc, 'tcx>( sym::floorf64 => "floor", sym::ceilf32 => "ceilf", sym::ceilf64 => "ceil", - sym::powf128 => return float_intrinsic(cx, cx.type_f128(), "powf128"), sym::truncf32 => "truncf", sym::truncf64 => "trunc", // We match the LLVM backend and lower this to `rint`. @@ -89,7 +89,6 @@ fn get_simple_intrinsic<'gcc, 'tcx>( sym::round_ties_even_f64 => "rint", sym::roundf32 => "roundf", sym::roundf64 => "round", - sym::abort => "abort", _ => return None, }; Some(cx.context.get_builtin_function(gcc_name)) @@ -181,14 +180,11 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc let simple = get_simple_intrinsic(self, name); let value = match name { - _ if simple.is_some() => { - let func = simple.expect("simple intrinsic function"); - self.cx.context.new_call( - self.location, - func, - &args.iter().map(|arg| arg.immediate()).collect::>(), - ) - } + _ if let Some(func) = simple => self.cx.context.new_call( + self.location, + func, + &args.iter().map(|arg| arg.immediate()).collect::>(), + ), // FIXME(antoyo): We can probably remove these and use the fallback intrinsic implementation. sym::minimumf32 | sym::minimumf64 | sym::maximumf32 | sym::maximumf64 => { let (ty, func_name) = match name { @@ -323,7 +319,9 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc unimplemented!(); } sym::va_arg => { - unimplemented!(); + let va_list = args[0].immediate(); + let gcc_type = self.immediate_backend_type(result.layout); + self.va_arg(va_list, gcc_type) } sym::volatile_load | sym::unaligned_volatile_load => { @@ -612,7 +610,7 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc self.on_stack_function_params.borrow_mut().insert(func, FxHashSet::default()); - crate::attributes::from_fn_attrs(self, func, instance); + crate::attributes::from_fn_attrs(self, func, instance, None); func }; @@ -664,16 +662,26 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc } fn abort(&mut self) { - let func = self.context.get_builtin_function("abort"); - let func: RValue<'gcc> = unsafe { std::mem::transmute(func) }; - self.call(self.type_void(), None, None, func, &[], None, None); + let func = self.context.get_builtin_function("__builtin_trap"); + self.block.add_eval(self.location, self.context.new_call(self.location, func, &[])); } fn assume(&mut self, value: Self::Value) { - // FIXME(antoyo): switch to assume when it exists. - // Or use something like this: - // #define __assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0) - self.expect(value, true); + // libgccjit currently has no direct equivalent of LLVM's `llvm.assume`, + // so use the idiom `if (!cond) __builtin_unreachable()`. + // FIXME: this should use IFN_ASSUME when we have internal functions in + // libgccjit. + if self.sess().opts.optimize == OptLevel::No { + return; + } + let then_block = self.append_sibling_block("assume_holds"); + let unreachable_block = self.append_sibling_block("assume_violated"); + self.block.end_with_conditional(self.location, value, then_block, unreachable_block); + + self.switch_to_block(unreachable_block); + self.unreachable(); + + self.switch_to_block(then_block); } fn expect(&mut self, cond: Self::Value, _expected: bool) -> Self::Value { @@ -691,8 +699,18 @@ impl<'a, 'gcc, 'tcx> IntrinsicCallBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tc self.context.new_rvalue_from_int(self.int_type, 0) } - fn va_start(&mut self, _va_list: RValue<'gcc>) { - unimplemented!(); + fn va_start(&mut self, va_list: RValue<'gcc>) { + let func = self.context.get_builtin_function("__builtin_va_start"); + + let va_list_type = self.context.new_c_type(CType::VaList); + let va_list = self.context.new_cast(self.location, va_list, va_list_type.make_pointer()); + + // Pre-C23 requires that the last "normal" argument was passed to va_start. + // Just pass 0, this appears to be handled correctly. + let last_normal_arg = self.context.new_rvalue_from_int(self.int_type, 0); + + let call = self.context.new_call(self.location, func, &[va_list, last_normal_arg]); + self.block.add_eval(self.location, call); } fn retag_reg(&mut self, _ptr: Self::Value, _info: &RetagInfo) -> Self::Value { @@ -950,7 +968,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { let else_block = func.new_block("else"); let after_block = func.new_block("after"); - let result = func.new_local(None, self.u32_type, "zeros"); + let result = self.new_temp(func, None, self.u32_type); let zero = self.cx.gcc_zero(arg.get_type()); let cond = self.gcc_icmp(IntPredicate::IntEQ, arg, zero); self.llbb().end_with_conditional(None, cond, then_block, else_block); @@ -1031,7 +1049,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { // else call it on the 64 high bits and add 64. In the else case, 64 high bits can't be 0 // because arg is not 0. - let result = self.current_func().new_local(None, result_type, "count_zeroes_results"); + let result = self.new_temp(self.current_func(), None, result_type); let cz_then_block = self.current_func().new_block("cz_then"); let cz_else_block = self.current_func().new_block("cz_else"); @@ -1146,8 +1164,8 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { let loop_tail = func.new_block("tail"); let counter_type = self.int_type; - let counter = self.current_func().new_local(None, counter_type, "popcount_counter"); - let val = self.current_func().new_local(None, value_type, "popcount_value"); + let counter = self.new_temp(self.current_func(), None, counter_type); + let val = self.new_temp(self.current_func(), None, value_type); let zero = self.gcc_zero(counter_type); self.llbb().add_assignment(self.location, counter, zero); self.llbb().add_assignment(self.location, val, value); diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/old_archs.rs b/compiler/rustc_codegen_gcc/src/intrinsic/old_archs.rs index 8d3e3487b5cb4..1aac52c28d220 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/old_archs.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/old_archs.rs @@ -1240,6 +1240,10 @@ pub(crate) fn old_archs(arch: &str, name: &str) -> ArchCheckResult { "avx512.vbroadcast.sd.pd.512" => "__builtin_ia32_vbroadcastsd_pd512", "avx512.vbroadcast.ss.512" => "__builtin_ia32_vbroadcastss512", "avx512.vbroadcast.ss.ps.512" => "__builtin_ia32_vbroadcastss_ps512", + "bmi.pdep.32" => "__builtin_ia32_pdep_si", + "bmi.pdep.64" => "__builtin_ia32_pdep_di", + "bmi.pext.32" => "__builtin_ia32_pext_si", + "bmi.pext.64" => "__builtin_ia32_pext_di", "fma.mask.vfmadd.pd.512" => "__builtin_ia32_vfmaddpd512_mask", "fma.mask.vfmadd.ps.512" => "__builtin_ia32_vfmaddps512_mask", "fma.mask.vfmaddsub.pd.512" => "__builtin_ia32_vfmaddsubpd512_mask", diff --git a/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs b/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs index 1416f4eec9c4a..02f1f7efd9c1e 100644 --- a/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs +++ b/compiler/rustc_codegen_gcc/src/intrinsic/simd.rs @@ -11,11 +11,12 @@ use rustc_codegen_ssa::diagnostics::ExpectedPointerMutability; use rustc_codegen_ssa::diagnostics::InvalidMonomorphization; use rustc_codegen_ssa::mir::operand::OperandRef; use rustc_codegen_ssa::mir::place::PlaceRef; -use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, BuilderMethods}; +use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, BuilderMethods, LayoutTypeCodegenMethods}; #[cfg(feature = "master")] use rustc_hir as hir; use rustc_middle::mir::BinOp; -use rustc_middle::ty::layout::HasTyCtxt; +use rustc_middle::span_bug; +use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf}; use rustc_middle::ty::{self, Ty}; use rustc_span::{ErrorGuaranteed, Span, Symbol, sym}; @@ -655,6 +656,39 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( return Ok(bx.context.new_rvalue_from_vector(bx.location, llret_ty, &values)); } + if name == sym::simd_arith_offset { + // This also checks that the first operand is a ptr type. + let pointee = in_elem.builtin_deref(true).unwrap_or_else(|| { + span_bug!(span, "must be called with a vector of pointer types as first argument") + }); + let layout = bx.layout_of(pointee); + // The second argument must be a ptr-sized integer. + // (We don't care about the signedness, this is wrapping anyway.) + let (_, offsets_elem) = args[1].layout.ty.simd_size_and_type(bx.tcx()); + if !matches!(offsets_elem.kind(), ty::Int(ty::IntTy::Isize) | ty::Uint(ty::UintTy::Usize)) { + span_bug!( + span, + "must be called with a vector of pointer-sized integers as second argument" + ); + } + + let pointee_type = bx.backend_type(layout); + let pointers = args[0].immediate(); + let offsets = args[1].immediate(); + let elem_type = llret_ty.dyncast_vector().expect("vector return type").get_element_type(); + let values: Vec<_> = (0..in_len) + .map(|i| { + let index = bx.context.new_rvalue_from_long(bx.usize_type, i as _); + let pointer = bx.extract_element(pointers, index); + let offset = bx.extract_element(offsets, index); + let pointer = bx.gep(pointee_type, pointer, &[offset]); + // GCC has no pointer vectors, so the lanes are `usize`. + bx.ptrtoint(pointer, elem_type) + }) + .collect(); + return Ok(bx.context.new_rvalue_from_vector(bx.location, llret_ty, &values)); + } + #[cfg(feature = "master")] if name == sym::simd_cast || name == sym::simd_as { require_simd!(ret_ty, InvalidMonomorphization::SimdReturn { span, name, ty: ret_ty }); @@ -675,20 +709,28 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( return Ok(args[0].immediate()); } + #[derive(Copy, Clone)] + enum Sign { + Unsigned, + Signed, + } + use Sign::*; + enum Style { Float, - Int, + Int(Sign), Unsupported, } let in_style = match *in_elem.kind() { - ty::Int(_) | ty::Uint(_) => Style::Int, + ty::Int(_) => Style::Int(Signed), + ty::Uint(_) => Style::Int(Unsigned), ty::Float(_) => Style::Float, _ => Style::Unsupported, }; - let out_style = match *out_elem.kind() { - ty::Int(_) | ty::Uint(_) => Style::Int, + ty::Int(_) => Style::Int(Signed), + ty::Uint(_) => Style::Int(Unsigned), ty::Float(_) => Style::Float, _ => Style::Unsupported, }; @@ -707,6 +749,19 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( } ); } + (Style::Float, Style::Int(sign)) if name == sym::simd_as => { + let vector = args[0].immediate(); + let elem_type = + llret_ty.dyncast_vector().expect("vector return type").get_element_type(); + let values: Vec<_> = (0..in_len) + .map(|i| { + let index = bx.context.new_rvalue_from_long(bx.usize_type, i as _); + let value = bx.extract_element(vector, index); + bx.cast_float_to_int(matches!(sign, Sign::Signed), value, elem_type) + }) + .collect(); + return Ok(bx.context.new_rvalue_from_vector(bx.location, llret_ty, &values)); + } _ => return Ok(bx.context.convert_vector(None, args[0].immediate(), llret_ty)), } } @@ -1310,32 +1365,28 @@ pub fn generic_simd_intrinsic<'a, 'gcc, 'tcx>( (true, false) => { // FIXME(antoyo): dyncast_vector should not require a call to unqualified. let arg_type = lhs.get_type().unqualified(); - // FIXME(antoyo): this uses the same algorithm from saturating add, but add the - // negative of the right operand. Find a proper subtraction algorithm. - let rhs = bx.context.new_unary_op(None, UnaryOp::Minus, arg_type, rhs); - // FIXME(antoyo): convert lhs and rhs to unsigned. - let sum = lhs + rhs; + let difference = lhs - rhs; let vector_type = arg_type.dyncast_vector().expect("vector type"); let unit = vector_type.get_num_units(); let a = bx.context.new_rvalue_from_int(elem_ty, ((elem_width as i32) << 3) - 1); let width = bx.context.new_rvalue_from_vector(None, lhs.get_type(), &vec![a; unit]); + // The subtraction overflows when the operands have different signs and the result + // has a different sign than the left operand. let xor1 = lhs ^ rhs; - let xor2 = lhs ^ sum; - let and = - bx.context.new_unary_op(None, UnaryOp::BitwiseNegate, arg_type, xor1) & xor2; - let mask = and >> width; + let xor2 = lhs ^ difference; + let mask = (xor1 & xor2) >> width; let one = bx.context.new_rvalue_one(elem_ty); let ones = bx.context.new_rvalue_from_vector(None, lhs.get_type(), &vec![one; unit]); let shift1 = ones << width; - let shift2 = sum >> width; + let shift2 = difference >> width; let mask_min = shift1 ^ shift2; - let and1 = - bx.context.new_unary_op(None, UnaryOp::BitwiseNegate, arg_type, mask) & sum; + let and1 = bx.context.new_unary_op(None, UnaryOp::BitwiseNegate, arg_type, mask) + & difference; let and2 = mask & mask_min; and1 + and2 diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index 2fb5459a20283..1e8eb4199ab3f 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -76,9 +76,9 @@ use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; -use gccjit::{CType, Context, OptimizationLevel}; #[cfg(feature = "master")] -use gccjit::{TargetInfo, Version}; +use gccjit::TargetInfo; +use gccjit::{CType, Context, OptimizationLevel}; use rustc_ast::expand::allocator::AllocatorMethod; use rustc_codegen_ssa::back::lto::ThinModule; use rustc_codegen_ssa::back::write::{ @@ -97,7 +97,7 @@ use rustc_middle::util::Providers; use rustc_session::config::{OptLevel, OutputFilenames}; use rustc_session::{IncrCompSession, Session}; use rustc_span::{Symbol, sym}; -use rustc_target::spec::{Arch, RelocModel}; +use rustc_target::spec::{RelocModel, TargetTuple}; use tempfile::TempDir; use crate::back::lto::ModuleBuffer; @@ -196,35 +196,44 @@ impl CodegenBackend for GccCodegenBackend { } fn init(&self, sess: &Session) { - fn file_path(sysroot_path: &Path, sess: &Session) -> PathBuf { - let rustlib_path = - rustc_target::relative_target_rustlib_path(sysroot_path, &sess.host.llvm_target); - sysroot_path - .join(rustlib_path) - .join("codegen-backends") - .join("lib") - .join(sess.target.llvm_target.as_ref()) - .join("libgccjit.so") + fn file_paths(sysroot_path: &Path, sess: &Session) -> Vec { + let rustlib_path = rustc_target::relative_target_rustlib_path( + sysroot_path, + rustc_session::config::host_tuple(), + ); + let lib_path = sysroot_path.join(rustlib_path).join("codegen-backends").join("lib"); + let rust_target_path = + lib_path.join(sess.opts.target_triple.tuple()).join("libgccjit.so"); + let mut paths = vec![rust_target_path]; + if matches!(sess.opts.target_triple, TargetTuple::TargetJson { .. }) { + let llvm_target_path = + lib_path.join(sess.target.llvm_target.as_ref()).join("libgccjit.so"); + paths.push(llvm_target_path); + } + paths } // We use all_paths() instead of only path() in case the path specified by --sysroot is // invalid. // This is the case for instance in Rust for Linux where they specify --sysroot=/dev/null. - for path in sess.opts.sysroot.all_paths() { - let libgccjit_target_lib_file = file_path(path, sess); - if let Ok(true) = fs::exists(&libgccjit_target_lib_file) { - load_libgccjit_if_needed(&libgccjit_target_lib_file); - break; + 'sysroot: for path in sess.opts.sysroot.all_paths() { + for libgccjit_target_lib_file in file_paths(path, sess) { + if let Ok(true) = fs::exists(&libgccjit_target_lib_file) { + load_libgccjit_if_needed(&libgccjit_target_lib_file); + break 'sysroot; + } } } if !gccjit::is_loaded() { let mut paths = vec![]; for path in sess.opts.sysroot.all_paths() { - let libgccjit_target_lib_file = file_path(path, sess); - paths.push(libgccjit_target_lib_file); + for libgccjit_target_lib_file in file_paths(path, sess) { + paths.push(libgccjit_target_lib_file); + } } + paths.dedup(); panic!("Could not load libgccjit.so. Attempted paths: {:#?}", paths); } @@ -286,7 +295,7 @@ impl CodegenBackend for GccCodegenBackend { } fn target_cpu(&self, sess: &Session) -> String { - target_cpu(sess).to_owned() + target_cpu(sess).into_owned() } fn codegen_crate(&self, tcx: TyCtxt<'_>) -> Box { @@ -316,27 +325,6 @@ impl CodegenBackend for GccCodegenBackend { } } -fn new_context<'gcc, 'tcx>(tcx: TyCtxt<'tcx>) -> Context<'gcc> { - let context = Context::default(); - if matches!(tcx.sess.target.arch, Arch::X86 | Arch::X86_64) { - context.add_command_line_option("-masm=intel"); - } - #[cfg(feature = "master")] - { - context.set_special_chars_allowed_in_func_names("$.*"); - let version = Version::get(); - let version = format!("{}.{}.{}", version.major, version.minor, version.patch); - context.set_output_ident(&format!( - "rustc version {} with libgccjit {}", - rustc_interface::util::rustc_version_str().unwrap_or("unknown version"), - version, - )); - } - // FIXME(antoyo): check if this should only be added when using -Cforce-unwind-tables=n. - context.add_command_line_option("-fno-asynchronous-unwind-tables"); - context -} - impl ExtraBackendMethods for GccCodegenBackend { type Module = GccContext; @@ -348,7 +336,7 @@ impl ExtraBackendMethods for GccCodegenBackend { ) -> Self::Module { let lto_supported = self.lto_supported.load(Ordering::SeqCst); let mut mods = GccContext { - context: Arc::new(SyncContext::new(new_context(tcx))), + context: Arc::new(SyncContext::new(gcc_util::new_context(tcx.sess))), relocation_model: tcx.sess.relocation_model(), lto_mode: LtoMode::None, lto_supported, @@ -446,7 +434,7 @@ impl WriteBackendMethods for GccCodegenBackend { each_linked_rlib_for_lto: &[PathBuf], modules: Vec>, ) -> CompiledModule { - back::lto::run_fat(cgcx, &sess.prof, shared_emitter, each_linked_rlib_for_lto, modules) + back::lto::run_fat(sess, cgcx, shared_emitter, each_linked_rlib_for_lto, modules) } fn run_thin_lto( diff --git a/compiler/rustc_codegen_gcc/src/mono_item.rs b/compiler/rustc_codegen_gcc/src/mono_item.rs index d5874779021d2..57411c854771c 100644 --- a/compiler/rustc_codegen_gcc/src/mono_item.rs +++ b/compiler/rustc_codegen_gcc/src/mono_item.rs @@ -1,15 +1,17 @@ +use gccjit::Function; #[cfg(feature = "master")] -use gccjit::{FnAttribute, VarAttribute}; +use gccjit::{FnAttribute, GlobalKind, ToRValue, Type, VarAttribute}; use rustc_codegen_ssa::traits::PreDefineCodegenMethods; use rustc_hir::attrs::Linkage; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_middle::bug; -use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; +use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}; use rustc_middle::mono::Visibility; use rustc_middle::ty::layout::{FnAbiOf, HasTypingEnv, LayoutOf}; use rustc_middle::ty::{self, Instance, TypeVisitableExt}; +use crate::consts::const_alloc_type; use crate::context::CodegenCx; use crate::type_of::LayoutGccExt; use crate::{attributes, base}; @@ -19,25 +21,59 @@ impl<'gcc, 'tcx> PreDefineCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { fn predefine_static( &mut self, def_id: DefId, - _linkage: Linkage, + linkage: Linkage, visibility: Visibility, - symbol_name: &str, + global_name: &str, ) { let attrs = self.tcx.codegen_fn_attrs(def_id); let instance = Instance::mono(self.tcx, def_id); - let DefKind::Static { nested, .. } = self.tcx.def_kind(def_id) else { bug!() }; - // Nested statics do not have a type, so pick a dummy type and let `codegen_static` figure out - // the gcc type from the actual evaluated initializer. - let ty = - if nested { self.tcx.types.unit } else { instance.ty(self.tcx, self.typing_env()) }; - let gcc_type = self.layout_of(ty).gcc_type(self); + // Declare the global with the type its initializer will have, so that `codegen_static` + // never has to retype it afterwards. The initializer is lowered as a packed struct of byte + // runs and relocations, which almost never matches the layout type. + let gcc_type = match self.tcx.eval_static_initializer(def_id) { + Ok(alloc) => const_alloc_type(self, alloc), + // The initializer failed to evaluate; `codegen_static` bails out on it too, so this + // type is never used to hold one. + Err(_) => { + let DefKind::Static { nested, .. } = self.tcx.def_kind(def_id) else { bug!() }; + // Nested statics do not have a type, so pick a dummy one. + let ty = if nested { + self.tcx.types.unit + } else { + instance.ty(self.tcx, self.typing_env()) + }; + self.layout_of(ty).gcc_type(self) + } + }; let is_tls = attrs.flags.contains(CodegenFnAttrFlags::THREAD_LOCAL); - let global = self.define_global(symbol_name, gcc_type, is_tls, attrs.link_section); + let global_kind = base::global_linkage_to_gcc(linkage); + let global = + self.define_global(global_name, gcc_type, global_kind, is_tls, attrs.link_section); + #[cfg(feature = "master")] + { + // Visibility is meaningless on an internal global: GCC ignores the attribute and + // warns about it. + if !matches!(global_kind, GlobalKind::Internal) { + // If we're compiling the compiler-builtins crate, e.g., the equivalent of + // compiler-rt, then we want to implicitly compile everything with hidden + // visibility as we're going to link this object all over the place but + // don't want the symbols to get exported. + let visibility = if self.tcx.is_compiler_builtins(LOCAL_CRATE) { + gccjit::Visibility::Hidden + } else { + base::visibility_to_gcc(visibility) + }; + global.add_attribute(VarAttribute::Visibility(visibility)); + } + if let Some(attribute) = base::global_linkage_attribute(linkage) { + global.add_attribute(attribute); + } + } + #[cfg(feature = "master")] - global.add_attribute(VarAttribute::Visibility(base::visibility_to_gcc(visibility))); + self.add_static_aliases(gcc_type, global_name, attrs, &attrs.foreign_item_symbol_aliases); - // FIXME(antoyo): set linkage. self.instances.borrow_mut().insert(instance, global); } @@ -50,12 +86,114 @@ impl<'gcc, 'tcx> PreDefineCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { ) { assert!(!instance.args.has_infer()); + let attrs = self.tcx.codegen_instance_attrs(instance.def); + + let decl = + self.predefine_without_aliases(instance, &attrs, linkage, visibility, symbol_name); + + #[cfg(feature = "master")] + self.add_function_aliases(instance, decl, &attrs, &attrs.foreign_item_symbol_aliases); + + self.functions.borrow_mut().insert(symbol_name.to_string(), decl); + self.function_instances.borrow_mut().insert(instance, decl); + } +} + +impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { + #[cfg(feature = "master")] + fn add_static_aliases( + &self, + gcc_type: Type<'gcc>, + aliased: &str, + attrs: &CodegenFnAttrs, + aliases: &[(DefId, Linkage, Visibility)], + ) { + let is_tls = attrs.flags.contains(CodegenFnAttrFlags::THREAD_LOCAL); + + for &(alias, linkage, visibility) in aliases { + let instance = Instance::mono(self.tcx, alias); + let symbol_name = self.tcx.symbol_name(instance); + + let alias = self.declare_global( + symbol_name.name, + gcc_type, + GlobalKind::Imported, + is_tls, + attrs.link_section, + ); + alias.add_attribute(VarAttribute::Visibility(base::visibility_to_gcc(visibility))); + alias.add_attribute(VarAttribute::Alias(aliased)); + if linkage == Linkage::WeakAny { + alias.add_attribute(VarAttribute::Weak); + } + + // Add the alias name to the set of cached items, so there is no duplicate + // instance added to it during the normal `external static` codegen + let prev_entry = self.instances.borrow_mut().insert(instance, alias); + + // If there already was a previous entry, then `add_static_aliases` was called multiple times for the same `alias` + // which would result in incorrect codegen + assert!(prev_entry.is_none(), "An instance was already present for {instance:?}"); + } + } + + #[cfg(feature = "master")] + fn add_function_aliases( + &self, + aliased_instance: Instance<'tcx>, + aliased: Function<'gcc>, + attrs: &CodegenFnAttrs, + aliases: &[(DefId, Linkage, Visibility)], + ) { + for &(alias, linkage, visibility) in aliases { + let symbol_name = self.tcx.symbol_name(Instance::mono(self.tcx, alias)); + + // predefine another copy of the original instance + // with a new symbol name + let alias_fn_decl = self.predefine_without_aliases( + aliased_instance, + attrs, + linkage, + visibility, + symbol_name.name, + ); + + let block = alias_fn_decl.new_block("start"); + let nb_params = alias_fn_decl.get_param_count(); + let mut args = Vec::with_capacity(nb_params); + for idx in 0..nb_params { + args.push(alias_fn_decl.get_param(idx as _).to_rvalue()); + } + + let void_type = self.context.new_type::<()>(); + let call = self.context.new_call(None, aliased, &args); + if alias_fn_decl.get_return_type() == void_type { + block.add_eval(None, call); + block.end_with_void_return(None); + } else { + block.end_with_return(None, call); + } + } + } + + fn predefine_without_aliases( + &self, + instance: Instance<'tcx>, + _attrs: &CodegenFnAttrs, + linkage: Linkage, + visibility: Visibility, + symbol_name: &str, + ) -> Function<'gcc> { let fn_abi = self.fn_abi_of_instance(instance, ty::List::empty()); self.linkage.set(base::linkage_to_gcc(linkage)); - let decl = self.declare_fn(symbol_name, fn_abi); - //let attrs = self.tcx.codegen_instance_attrs(instance.def); + let fn_decl = self.declare_fn(symbol_name, fn_abi); + + attributes::from_fn_attrs(self, fn_decl, instance, Some(fn_abi)); - attributes::from_fn_attrs(self, decl, instance); + #[cfg(feature = "master")] + if base::linkage_needs_weak_attribute(linkage) { + fn_decl.add_attribute(FnAttribute::Weak); + } // If we're compiling the compiler-builtins crate, e.g., the equivalent of // compiler-rt, then we want to implicitly compile everything with hidden @@ -63,17 +201,21 @@ impl<'gcc, 'tcx> PreDefineCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { // don't want the symbols to get exported. if linkage != Linkage::Internal && self.tcx.is_compiler_builtins(LOCAL_CRATE) { #[cfg(feature = "master")] - decl.add_attribute(FnAttribute::Visibility(gccjit::Visibility::Hidden)); + fn_decl.add_attribute(FnAttribute::Visibility(gccjit::Visibility::Hidden)); } else if visibility != Visibility::Default { #[cfg(feature = "master")] - decl.add_attribute(FnAttribute::Visibility(base::visibility_to_gcc(visibility))); + fn_decl.add_attribute(FnAttribute::Visibility(base::visibility_to_gcc(visibility))); + } + + #[cfg(feature = "master")] + if let Some(section) = _attrs.link_section { + fn_decl.add_attribute(FnAttribute::Section(section.as_str())); } - // FIXME(antoyo): call set_link_section() to allow initializing argc/argv. // FIXME(antoyo): set unique comdat. // FIXME(antoyo): use inline attribute from there in linkage.set() above. + // FIXME: Should we handle dso? - self.functions.borrow_mut().insert(symbol_name.to_string(), decl); - self.function_instances.borrow_mut().insert(instance, decl); + fn_decl } } diff --git a/compiler/rustc_codegen_gcc/src/type_.rs b/compiler/rustc_codegen_gcc/src/type_.rs index 5252f93a92ebe..9b2896838e735 100644 --- a/compiler/rustc_codegen_gcc/src/type_.rs +++ b/compiler/rustc_codegen_gcc/src/type_.rs @@ -1,8 +1,9 @@ #[cfg(feature = "master")] use std::convert::TryInto; +use std::mem::discriminant; #[cfg(feature = "master")] -use gccjit::CType; +use gccjit::{CType, TypeAttribute}; use gccjit::{RValue, Struct, Type}; use rustc_abi::{AddressSpace, Align, Integer, Size}; use rustc_codegen_ssa::common::TypeKind; @@ -101,9 +102,10 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { self.bool_type } - pub fn type_struct(&self, fields: &[Type<'gcc>], packed: bool) -> Type<'gcc> { - let types = fields.to_vec(); - if let Some(typ) = self.struct_types.borrow().get(fields) { + pub fn type_struct(&self, fields: &[Type<'gcc>], attributes: &[StructAttribute]) -> Type<'gcc> { + let key = + StructTypeKey { fields: fields.to_vec(), attributes: canonical_attributes(attributes) }; + if let Some(typ) = self.struct_types.borrow().get(&key) { return *typ; } let fields: Vec<_> = fields @@ -114,15 +116,91 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { }) .collect(); let typ = self.context.new_struct_type(None, "struct", &fields).as_type(); - if packed { - #[cfg(feature = "master")] - typ.set_packed(); - } - self.struct_types.borrow_mut().insert(types, typ); + apply_struct_attributes(typ, &key.attributes); + self.struct_types.borrow_mut().insert(key, typ); typ } } +/// An attribute that can be set on a GCC struct type. +/// +/// This mirrors the subset of `gccjit::TypeAttribute` that cg_gcc needs, rather than using it +/// directly, because it must exist without the `master` feature and because it is what +/// `StructTypeKey` is keyed on. Adding a variant here is therefore all it takes to make a new +/// attribute part of the cache key: there is no second place to remember to update. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum StructAttribute { + /// Alignment, in bytes. + Aligned(u32), + /// Lay the fields out without inserting padding between them. + Packed, +} + +/// Identifies an anonymous struct type in `CodegenCx::struct_types`. +/// +/// Two Rust types with the same field list can still need distinct GCC types — `struct { a: u64, +/// b: u64 }` with and without `repr(align(16))` produces the same fields — so every attribute has +/// to be part of the key. Holding them as one [`StructAttribute`] list rather than as separate +/// fields is what keeps that true when a new attribute is added. +#[derive(Clone, Eq, Hash, PartialEq)] +pub struct StructTypeKey<'gcc> { + pub fields: Vec>, + pub attributes: Vec, +} + +/// The attributes a GCC struct needs in order to match the Rust layout it is built from. +pub fn struct_attributes(packed: bool, align: Option) -> Vec { + let mut attributes = Vec::new(); + if packed { + attributes.push(StructAttribute::Packed); + } + if let Some(align) = align + && align.bytes() > 1 + && align.bytes() <= MAX_STRUCT_ALIGNMENT + { + attributes.push(StructAttribute::Aligned(align.bytes() as u32)); + } + attributes +} + +/// The largest alignment GCC accepts on a type, in bytes. +const MAX_STRUCT_ALIGNMENT: u64 = 1 << 28; + +/// Put an attribute list into a canonical form so that it can be used as a cache key. +/// +/// Without this, `[Packed, Aligned(8)]` and `[Aligned(8), Packed]` would hash differently and mint +/// two GCC types for what is one Rust type. +fn canonical_attributes(attributes: &[StructAttribute]) -> Vec { + let mut attributes = attributes.to_vec(); + attributes.sort_unstable(); + attributes.dedup(); + debug_assert!( + attributes.windows(2).all(|pair| discriminant(&pair[0]) != discriminant(&pair[1])), + "contradictory struct attributes: {attributes:?}" + ); + attributes +} + +/// Set `attributes` on the struct type `typ`. +/// +/// This is the only place allowed to call `Type::add_attribute`; `clippy.toml` forbids it +/// everywhere else. An attribute set on a type that `CodegenCx::struct_types` handed out would +/// change every other use of that type, so attributes have to be decided when the type is created +/// and be part of its cache key. Going through [`StructAttribute`] is what enforces that. +#[cfg(feature = "master")] +#[allow(clippy::disallowed_methods)] +pub fn apply_struct_attributes(typ: Type<'_>, attributes: &[StructAttribute]) { + for attribute in attributes { + typ.add_attribute(match *attribute { + StructAttribute::Aligned(align) => TypeAttribute::Aligned(align), + StructAttribute::Packed => TypeAttribute::Packed, + }); + } +} + +#[cfg(not(feature = "master"))] +pub fn apply_struct_attributes(_typ: Type<'_>, _attributes: &[StructAttribute]) {} + impl<'gcc, 'tcx> BaseTypeCodegenMethods for CodegenCx<'gcc, 'tcx> { fn type_i8(&self) -> Type<'gcc> { self.i8_type @@ -153,7 +231,7 @@ impl<'gcc, 'tcx> BaseTypeCodegenMethods for CodegenCx<'gcc, 'tcx> { if self.supports_f16_type { return self.context.new_c_type(CType::Float16); } - bug!("unsupported float width 16") + self.u16_type } fn type_f32(&self) -> Type<'gcc> { @@ -324,17 +402,19 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { self.type_array(self.type_from_integer(unit), size / unit_size) } - pub fn set_struct_body(&self, typ: Struct<'gcc>, fields: &[Type<'gcc>], packed: bool) { + pub fn set_struct_body( + &self, + typ: Struct<'gcc>, + fields: &[Type<'gcc>], + attributes: &[StructAttribute], + ) { let fields: Vec<_> = fields .iter() .enumerate() .map(|(index, field)| self.context.new_field(None, *field, format!("field_{}", index))) .collect(); typ.set_fields(None, &fields); - if packed { - #[cfg(feature = "master")] - typ.as_type().set_packed(); - } + apply_struct_attributes(typ.as_type(), &canonical_attributes(attributes)); } pub fn type_named_struct(&self, name: &str) -> Struct<'gcc> { diff --git a/compiler/rustc_codegen_gcc/src/type_of.rs b/compiler/rustc_codegen_gcc/src/type_of.rs index 53192c0a087e4..ce52ecab6480c 100644 --- a/compiler/rustc_codegen_gcc/src/type_of.rs +++ b/compiler/rustc_codegen_gcc/src/type_of.rs @@ -17,7 +17,7 @@ use rustc_target::callconv::{CastTarget, FnAbi}; use crate::abi::{FnAbiGcc, FnAbiGccExt, GccType}; use crate::context::CodegenCx; -use crate::type_::struct_fields; +use crate::type_::{struct_attributes, struct_fields}; impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { fn type_from_unsigned_integer(&self, i: Integer) -> Type<'gcc> { @@ -81,7 +81,7 @@ fn uncached_gcc_type<'gcc, 'tcx>( layout.scalar_pair_element_gcc_type(cx, 0), layout.scalar_pair_element_gcc_type(cx, 1), ], - false, + &struct_attributes(false, Some(layout.align.abi)), ); } BackendRepr::Memory { .. } => {} @@ -129,11 +129,12 @@ fn uncached_gcc_type<'gcc, 'tcx>( FieldsShape::Primitive | FieldsShape::Union(_) => { let fill = cx.type_padding_filler(layout.size, layout.align.abi); let packed = false; + let attributes = struct_attributes(packed, Some(layout.align.abi)); match name { - None => cx.type_struct(&[fill], packed), + None => cx.type_struct(&[fill], &attributes), Some(ref name) => { let gcc_type = cx.type_named_struct(name); - cx.set_struct_body(gcc_type, &[fill], packed); + cx.set_struct_body(gcc_type, &[fill], &attributes); gcc_type.as_type() } } @@ -142,7 +143,7 @@ fn uncached_gcc_type<'gcc, 'tcx>( FieldsShape::Arbitrary { .. } => match name { None => { let (gcc_fields, packed) = struct_fields(cx, layout); - cx.type_struct(&gcc_fields, packed) + cx.type_struct(&gcc_fields, &struct_attributes(packed, Some(layout.align.abi))) } Some(ref name) => { let gcc_type = cx.type_named_struct(name); @@ -240,7 +241,11 @@ impl<'tcx> LayoutGccExt<'tcx> for TyAndLayout<'tcx> { if let Some((deferred_ty, layout)) = defer { let (fields, packed) = struct_fields(cx, layout); - cx.set_struct_body(deferred_ty, &fields, packed); + cx.set_struct_body( + deferred_ty, + &fields, + &struct_attributes(packed, Some(layout.align.abi)), + ); } ty diff --git a/compiler/rustc_codegen_gcc/tests/asm/asm/comments.rs b/compiler/rustc_codegen_gcc/tests/asm/asm/comments.rs new file mode 100644 index 0000000000000..603bb014930c4 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/asm/comments.rs @@ -0,0 +1,12 @@ +//@ assembly-output: emit-asm +//@ only-x86_64 +// Check that comments in assembly get passed + +#![crate_type = "lib"] + +// CHECK-LABEL: "test_comments": +#[no_mangle] +pub fn test_comments() { + // CHECK: example comment + unsafe { core::arch::asm!("nop // example comment") }; +} diff --git a/compiler/rustc_codegen_gcc/tests/asm/bulk_memory_alignment.rs b/compiler/rustc_codegen_gcc/tests/asm/bulk_memory_alignment.rs new file mode 100644 index 0000000000000..6115467447104 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/bulk_memory_alignment.rs @@ -0,0 +1,45 @@ +//@ assembly-output: emit-asm +//@ only-x86_64-unknown-linux-gnu +//@ compile-flags: -Copt-level=3 + +#![crate_type = "lib"] + +// The alignment reaches GCC's `memcpy`/`memset` expansion only through +// `__builtin_assume_aligned`; a pointer cast to an aligned type is stripped as a useless +// conversion. An over-aligned type therefore has to expand to aligned moves and a packed one +// to unaligned moves. The alignment is 64 so that the contrast holds whatever vector width +// the host picks. + +#[repr(align(64))] +pub struct Aligned([u8; 64]); + +#[repr(C, packed)] +pub struct Packed([u8; 64]); + +// CHECK-LABEL: "copy_aligned": +// CHECK: {{(v)?mov(dqa|aps)}} +#[no_mangle] +pub unsafe fn copy_aligned(destination: *mut Aligned, source: *const Aligned) { + core::ptr::copy_nonoverlapping(source, destination, 1); +} + +// CHECK-LABEL: "copy_packed": +// CHECK: {{(v)?mov(dqu|ups)}} +#[no_mangle] +pub unsafe fn copy_packed(destination: *mut Packed, source: *const Packed) { + core::ptr::copy_nonoverlapping(source, destination, 1); +} + +// CHECK-LABEL: "set_aligned": +// CHECK: {{(v)?mov(dqa|aps)}} +#[no_mangle] +pub unsafe fn set_aligned(destination: *mut Aligned) { + core::ptr::write_bytes(destination, 0, 1); +} + +// CHECK-LABEL: "set_packed": +// CHECK: {{(v)?mov(dqu|ups)}} +#[no_mangle] +pub unsafe fn set_packed(destination: *mut Packed) { + core::ptr::write_bytes(destination, 0, 1); +} diff --git a/compiler/rustc_codegen_gcc/tests/asm/naked-functions/x86_64-naked-fn-no-cet-prolog.rs b/compiler/rustc_codegen_gcc/tests/asm/naked-functions/x86_64-naked-fn-no-cet-prolog.rs new file mode 100644 index 0000000000000..81ee9b13b4eca --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/naked-functions/x86_64-naked-fn-no-cet-prolog.rs @@ -0,0 +1,24 @@ +//@ compile-flags: -C no-prepopulate-passes -Zcf-protection=full +//@ assembly-output: emit-asm +//@ needs-asm-support +//@ only-x86_64 + +#![crate_type = "lib"] + +use std::arch::naked_asm; + +// The problem at hand: Rust has adopted a fairly strict meaning for "naked functions", +// meaning "no prologue whatsoever, no, really, not one instruction." +// Unfortunately, x86's control-flow enforcement, specifically indirect branch protection, +// works by using an instruction for each possible landing site, +// and LLVM implements this via making sure of that. +#[no_mangle] +#[unsafe(naked)] +pub extern "sysv64" fn will_halt() -> ! { + // CHECK-NOT: endbr{{32|64}} + // CHECK: hlt + naked_asm!("hlt") +} + +// what about aarch64? +// "branch-protection"=false diff --git a/compiler/rustc_codegen_gcc/tests/asm/panic-no-unwind-no-uwtable.rs b/compiler/rustc_codegen_gcc/tests/asm/panic-no-unwind-no-uwtable.rs new file mode 100644 index 0000000000000..b51b173e9616e --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/panic-no-unwind-no-uwtable.rs @@ -0,0 +1,8 @@ +//@ assembly-output: emit-asm +//@ only-x86_64-unknown-linux-gnu +//@ compile-flags: -C panic=unwind -C force-unwind-tables=n -Copt-level=3 + +#![crate_type = "lib"] + +// CHECK-NOT: .cfi_startproc +pub fn foo() {} diff --git a/compiler/rustc_codegen_gcc/tests/asm/used.rs b/compiler/rustc_codegen_gcc/tests/asm/used.rs new file mode 100644 index 0000000000000..deb0c69dc48fa --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/used.rs @@ -0,0 +1,14 @@ +//@ assembly-output: emit-asm +//@ only-x86_64-unknown-linux-gnu + +#![feature(used_with_arg)] +#![crate_type = "lib"] + +// CHECK: .section .rodata.X,"a" +#[used(compiler)] +#[no_mangle] +pub static X: u32 = 12; +// CHECK: .section .rodata.Y,"aR" +#[used(linker)] +#[no_mangle] +pub static Y: u32 = 12; diff --git a/compiler/rustc_codegen_gcc/tests/asm/volatile_bulk_memory.rs b/compiler/rustc_codegen_gcc/tests/asm/volatile_bulk_memory.rs new file mode 100644 index 0000000000000..6233b8ac74c0e --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/volatile_bulk_memory.rs @@ -0,0 +1,37 @@ +//@ assembly-output: emit-asm +//@ only-x86_64-unknown-linux-gnu +//@ compile-flags: -Copt-level=3 + +#![feature(core_intrinsics)] +#![crate_type = "lib"] + +use std::intrinsics::{ + volatile_copy_memory, volatile_copy_nonoverlapping_memory, volatile_set_memory, +}; + +// The buffers below are never read back, so the writes only survive because they are volatile. +// The functions are ordered alphabetically because that is the order they are emitted in. + +// CHECK-LABEL: "volatile_copy": +// CHECK: mov +#[no_mangle] +pub unsafe fn volatile_copy(source: *const u8) { + let mut buffer = [1u8; 64]; + volatile_copy_memory(buffer.as_mut_ptr(), source, 64); +} + +// CHECK-LABEL: "volatile_copy_nonoverlapping": +// CHECK: mov +#[no_mangle] +pub unsafe fn volatile_copy_nonoverlapping(source: *const u8) { + let mut buffer = [1u8; 64]; + volatile_copy_nonoverlapping_memory(buffer.as_mut_ptr(), source, 64); +} + +// CHECK-LABEL: "volatile_set": +// CHECK: mov +#[no_mangle] +pub unsafe fn volatile_set() { + let mut buffer = [1u8; 64]; + volatile_set_memory(buffer.as_mut_ptr(), 0, 64); +} diff --git a/compiler/rustc_codegen_gcc/tests/asm/x86_64-sse_crc.rs b/compiler/rustc_codegen_gcc/tests/asm/x86_64-sse_crc.rs new file mode 100644 index 0000000000000..bde58955a2146 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/asm/x86_64-sse_crc.rs @@ -0,0 +1,12 @@ +//@ only-x86_64 +//@ assembly-output: emit-asm +//@ compile-flags: --crate-type staticlib -Ctarget-feature=+sse4.2 + +// CHECK-LABEL: banana +// CHECK: crc32 +#[no_mangle] +pub unsafe fn banana(v: u8) -> u32 { + use std::arch::x86_64::*; + let out = !0u32; + _mm_crc32_u8(out, v) +} diff --git a/compiler/rustc_codegen_gcc/tests/c/import_linkage.c b/compiler/rustc_codegen_gcc/tests/c/import_linkage.c new file mode 100644 index 0000000000000..f2beb9603d08b --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/c/import_linkage.c @@ -0,0 +1,17 @@ +/* The symbols that `tests/run/import_linkage.rs` imports with an explicit `#[linkage]`. + * + * Such an import is a pointer whose value is the address of the symbol, so what the Rust side + * reads back is `&value_*`, not the pointer stored in it. The distinct values make a mix-up + * visible. */ + +#include + +int32_t external_value = 1; +int32_t available_externally_value = 2; +int32_t linkonce_value = 3; +int32_t linkonce_odr_value = 4; +int32_t weak_value = 5; +int32_t weak_odr_value = 6; +int32_t common_value = 7; +int32_t extern_weak_value = 8; +int32_t internal_value = 9; diff --git a/compiler/rustc_codegen_gcc/tests/c/overaligned_byval_abi.c b/compiler/rustc_codegen_gcc/tests/c/overaligned_byval_abi.c new file mode 100644 index 0000000000000..826a104f185ea --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/c/overaligned_byval_abi.c @@ -0,0 +1,54 @@ +/* Reference side of `tests/run/overaligned_byval_abi.rs`, compiled by the real GCC. + * + * `Aligned` is an over-aligned aggregate passed by value ("byval"): the ABI places it in a stack + * slot aligned to its own alignment, not packed right after the preceding argument. cg_gcc used + * to build the GCC struct type from the field list alone, which dropped Rust's `repr(align(64))`, + * so it placed the argument at an offset nobody else agreed on. + * + * The two functions here check both directions: `c_take_both` is a GCC-built callee for a cg_gcc + * caller, and `c_call_rust` is a GCC-built caller for a cg_gcc callee. + * + * The checks are on the *values* received rather than on the address of the argument: which + * alignment the ABI gives a stack slot is target-specific, but caller and callee agreeing on it + * is not. A disagreement makes the arguments arrive as garbage. */ + +#include + +struct Big { + int64_t a, b, c; +}; + +struct __attribute__((aligned(64))) Aligned { + int32_t x; +}; + +/* Defined on the Rust side. */ +extern int32_t rust_take_both(struct Big first, struct Aligned second, struct Big third, + struct Aligned fourth); + +/* Called from Rust: checks what a cg_gcc caller passed. */ +int32_t c_take_both(struct Big first, struct Aligned second, struct Big third, + struct Aligned fourth) +{ + if (first.a != 1 || first.b != 2 || first.c != 3) + return 1; + if (second.x != 42) + return 2; + if (third.a != 4 || third.b != 5 || third.c != 6) + return 3; + if (fourth.x != 43) + return 4; + return 0; +} + +/* Called from Rust: passes the arguments the way the ABI says, for a cg_gcc callee to read. */ +int32_t c_call_rust(void) +{ + struct Big first = {1, 2, 3}; + struct Big third = {4, 5, 6}; + struct Aligned second, fourth; + + second.x = 42; + fourth.x = 43; + return rust_take_both(first, second, third, fourth); +} diff --git a/compiler/rustc_codegen_gcc/tests/c/static_linkage.c b/compiler/rustc_codegen_gcc/tests/c/static_linkage.c new file mode 100644 index 0000000000000..787e61f9cf105 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/c/static_linkage.c @@ -0,0 +1,37 @@ +/* Strong definitions of the statics that `tests/run/static_linkage.rs` also defines, but weakly. + * The linker has to keep these and drop the Rust ones; a backend that emits the Rust definitions + * as ordinary global symbols fails the link with a duplicate definition instead. + * + * `internal_static` is the opposite case: the Rust side keeps its own, and the two definitions + * coexist because the Rust one is local. */ + +#include + +int32_t weak_static = 1; +int32_t weak_odr_static = 2; +int32_t linkonce_static = 3; +int32_t linkonce_odr_static = 4; +int32_t common_static = 5; +int32_t internal_static = 200; + +/* `available_externally` promises the real definition lives elsewhere: a backend may read this one + * or emit an equivalent copy of the Rust initializer, so the two have to hold the same value. */ +int32_t available_externally_static = 7; + +/* Called from Rust, so that the reads also happen in a translation unit GCC compiled. */ +int32_t c_read_all(void) +{ + if (weak_static != 1) + return 11; + if (weak_odr_static != 2) + return 12; + if (linkonce_static != 3) + return 13; + if (linkonce_odr_static != 4) + return 14; + if (common_static != 5) + return 15; + if (internal_static != 200) + return 16; + return 0; +} diff --git a/compiler/rustc_codegen_gcc/tests/c/weak_function_linkage.c b/compiler/rustc_codegen_gcc/tests/c/weak_function_linkage.c new file mode 100644 index 0000000000000..25dcdedbcd950 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/c/weak_function_linkage.c @@ -0,0 +1,54 @@ +/* Strong definitions of the functions that `tests/run/weak_function_linkage.rs` also defines, but + * weakly. The linker has to keep these and drop the Rust ones. + * + * A backend that emits the Rust definitions as ordinary global symbols does not merely pick the + * wrong one: the link fails outright with a duplicate definition. */ + +#include + +int32_t weak_function(void) +{ + return 1; +} + +int32_t weak_odr_function(void) +{ + return 2; +} + +int32_t linkonce_function(void) +{ + return 3; +} + +int32_t linkonce_odr_function(void) +{ + return 4; +} + +int32_t weak_inline_function(void) +{ + return 8; +} + +/* `available_externally` promises the real definition lives elsewhere: a backend may call this one + * or emit an equivalent copy of the Rust body, so the two have to return the same value. */ +int32_t available_externally_function(void) +{ + return 7; +} + +/* Called from Rust, so that the calls also go through a caller that GCC compiled: a cg_gcc caller + * could inline the weak body it can see instead of calling the symbol. */ +int32_t c_call_all(void) +{ + if (weak_function() != 1) + return 11; + if (weak_odr_function() != 2) + return 12; + if (linkonce_function() != 3) + return 13; + if (linkonce_odr_function() != 4) + return 14; + return 0; +} diff --git a/compiler/rustc_codegen_gcc/tests/compile/asm_noreturn_call.rs b/compiler/rustc_codegen_gcc/tests/compile/asm_noreturn_call.rs new file mode 100644 index 0000000000000..c9238697d8097 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/compile/asm_noreturn_call.rs @@ -0,0 +1,15 @@ +// Compiler: + +// Regression test for https://github.com/rust-lang/rustc_codegen_gcc/issues/827 + +#![crate_type = "lib"] + +#[cfg(target_arch = "x86_64")] +pub type NoReturn = extern "sysv64" fn(&'static u8) -> !; + +#[cfg(target_arch = "x86_64")] +pub fn call_no_return(function: *const NoReturn) -> ! { + unsafe { + std::arch::asm!("call {}", in(reg) function, options(noreturn)); + } +} diff --git a/compiler/rustc_codegen_gcc/tests/compile/x86_interrupt_first_arg_byval.rs b/compiler/rustc_codegen_gcc/tests/compile/x86_interrupt_first_arg_byval.rs new file mode 100644 index 0000000000000..4b6bbd48f7ad5 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/compile/x86_interrupt_first_arg_byval.rs @@ -0,0 +1,16 @@ +// Compiler: + +// Test that `x86-interrupt` functions whose first argument is passed by value +// emit pointer-shaped GCC parameters and compile with interrupt-safe target features. + +#![feature(abi_x86_interrupt)] +#![crate_type = "lib"] + +#[repr(C)] +pub struct Frame { + ip: u64, +} + +pub extern "x86-interrupt" fn scalar(_a: i64) {} + +pub extern "x86-interrupt" fn aggregate(_frame: Frame) {} diff --git a/compiler/rustc_codegen_gcc/tests/cpuid.def b/compiler/rustc_codegen_gcc/tests/cpuid.def new file mode 100644 index 0000000000000..05fe8e94a8282 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/cpuid.def @@ -0,0 +1,27 @@ +# Input => Output +# EAX ECX => EAX EBX ECX EDX +00000000 ******** => 00000024 756e6547 6c65746e 49656e69 #Processor ID and Manufacturer +00000001 ******** => 00400f10 00100800 7ffaf3ff bfebfbff +00000007 00000000 => 00000002 f3bfbfbf bac05ffe 03d54130 #Extended Features +00000007 00000001 => 98ee00bf 00000002 00000020 1d29cd3e +0000000d 00000000 => 000e02e7 00002b00 00002b00 00000000 #xcr0 +0000000d 00000001 => 0000001f 00000240 00000100 00000000 #Supervisor State +0000000d 00000002 => 00000100 00000240 00000000 00000000 +0000000d 00000005 => 00000040 00000440 00000000 00000000 #zmasks +0000000d 00000006 => 00000200 00000480 00000000 00000000 #zmmh +0000000d 00000007 => 00000400 00000680 00000000 00000000 #zmm +0000000d 00000011 => 00000040 00000ac0 00000002 00000000 #tileconfig +0000000d 00000012 => 00002000 00000b00 00000006 00000000 #tiles +0000000d 00000013 => 00000080 000003c0 00000000 00000000 #APX +00000019 ******** => 00000000 00000005 00000000 00000000 #Key Locker +0000001d 00000000 => 00000001 00000000 00000000 00000000 #AMX Tile +0000001d 00000001 => 04002000 00080040 00000010 00000000 #AMX Palette1 +0000001e 00000000 => 00000001 00004010 00000000 00000000 #AMX Tmul +0000001e 00000001 => 000001ff 00000000 00000000 00000000 +00000024 00000000 => 00000001 00070002 00000000 00000000 #AVX10 +00000024 00000001 => 00000000 00000000 00000004 00000000 +80000000 ******** => 80000004 00000000 00000000 00000000 +80000001 ******** => 00000000 00000000 00000121 2c100000 +80000002 ******** => 00000000 00000000 00000000 00000000 +80000003 ******** => 00000000 00000000 00000000 00000000 +80000004 ******** => 00000000 00000000 00000000 00000000 diff --git a/compiler/rustc_codegen_gcc/tests/failing-ice-tests.txt b/compiler/rustc_codegen_gcc/tests/failing-ice-tests.txt index ff1b6f1489468..ca685eb2af71c 100644 --- a/compiler/rustc_codegen_gcc/tests/failing-ice-tests.txt +++ b/compiler/rustc_codegen_gcc/tests/failing-ice-tests.txt @@ -10,7 +10,6 @@ tests/ui/simd/intrinsic/generic-arithmetic-saturating-2.rs tests/ui/simd/intrinsic/generic-arithmetic-2.rs tests/ui/panics/default-backtrace-ice.rs tests/ui/mir/lint/storage-live.rs -tests/ui/layout/valid_range_oob.rs tests/ui/higher-ranked/trait-bounds/future.rs tests/ui/consts/const-eval/const-eval-query-stack.rs tests/ui/simd/masked-load-store.rs @@ -28,13 +27,17 @@ tests/ui/lto/thin-lto-global-allocator.rs tests/ui/lto/msvc-imp-present.rs tests/ui/lto/dylib-works.rs tests/ui/lto/all-crates.rs -tests/ui/issues/issue-47364.rs +tests/ui/codegen/no-segfault-with-multiple-codegen-units.rs tests/ui/functions-closures/parallel-codegen-closures.rs tests/ui/sepcomp/sepcomp-unwind.rs tests/ui/extern/issue-64655-extern-rust-must-allow-unwind.rs tests/ui/extern/issue-64655-allow-unwind-when-calling-panic-directly.rs -tests/ui/unwind-no-uwtable.rs +tests/ui/panics/unwind-force-no-unwind-tables.rs tests/ui/delegation/fn-header.rs tests/ui/simd/intrinsic/generic-arithmetic-pass.rs -tests/ui/simd/masked-load-store.rs -tests/ui/rfcs/rfc-2632-const-trait-impl/effects/minicore.rs +tests/ui/codegen/unknown-llvm-intrinsic.rs +tests/ui/codegen/incorrect-llvm-intrinsic-signature.rs +tests/ui/codegen/incorrect-arch-intrinsic.rs +tests/ui/codegen/custom-target-invalid-llvm-target.rs +tests/ui/asm/x86_64/naked_asm_escape.rs +tests/ui/lto/debuginfo-lto-alloc.rs diff --git a/compiler/rustc_codegen_gcc/tests/failing-lto-tests.txt b/compiler/rustc_codegen_gcc/tests/failing-lto-tests.txt index 4c62c35a512c1..7527005321991 100644 --- a/compiler/rustc_codegen_gcc/tests/failing-lto-tests.txt +++ b/compiler/rustc_codegen_gcc/tests/failing-lto-tests.txt @@ -1,6 +1,5 @@ tests/ui/lto/debuginfo-lto-alloc.rs -tests/ui/panic-runtime/lto-unwind.rs -tests/ui/uninhabited/uninhabited-transparent-return-abi.rs -tests/ui/coroutine/panic-drops-resume.rs -tests/ui/coroutine/panic-drops.rs -tests/ui/coroutine/panic-safe.rs +tests/ui/extern/issue-64655-allow-unwind-when-calling-panic-directly.rs +tests/ui/extern/issue-64655-extern-rust-must-allow-unwind.rs +tests/ui/lto/thin-lto-inlines2.rs +tests/ui/lto/lto-thin-rustc-loads-linker-plugin.rs diff --git a/compiler/rustc_codegen_gcc/tests/failing-run-make-tests.txt b/compiler/rustc_codegen_gcc/tests/failing-run-make-tests.txt index 528ee1df9f583..d5297e069f72f 100644 --- a/compiler/rustc_codegen_gcc/tests/failing-run-make-tests.txt +++ b/compiler/rustc_codegen_gcc/tests/failing-run-make-tests.txt @@ -11,4 +11,5 @@ tests/run-make/foreign-exceptions/ tests/run-make/glibc-staticlib-args/ tests/run-make/lto-smoke-c/ tests/run-make/return-non-c-like-enum/ -tests/run-make/short-ice +tests/run-make/short-ice/ +tests/run-make/embed-source-dwarf/ diff --git a/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt b/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt index e8a26a90890c1..7f96bfabedbea 100644 --- a/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt +++ b/compiler/rustc_codegen_gcc/tests/failing-ui-tests.txt @@ -1,113 +1,21 @@ tests/ui/asm/may_unwind.rs tests/ui/asm/x86_64/may_unwind.rs -tests/ui/drop/dynamic-drop-async.rs -tests/ui/cfg/cfg-panic-abort.rs tests/ui/intrinsics/panic-uninitialized-zeroed.rs -tests/ui/iterators/iter-sum-overflow-debug.rs -tests/ui/iterators/iter-sum-overflow-overflow-checks.rs -tests/ui/mir/mir_drop_order.rs -tests/ui/mir/mir_let_chains_drop_order.rs -tests/ui/mir/mir_match_guard_let_chains_drop_order.rs -tests/ui/panic-runtime/abort-link-to-unwinding-crates.rs -tests/ui/panic-runtime/abort.rs -tests/ui/panic-runtime/link-to-abort.rs -tests/ui/parser/unclosed-delimiter-in-dep.rs -tests/ui/consts/missing_span_in_backtrace.rs -tests/ui/drop/dynamic-drop.rs tests/ui/simd/issue-17170.rs tests/ui/simd/issue-39720.rs -tests/ui/drop/panic-during-drop-14875.rs -tests/ui/issues/issue-29948.rs tests/ui/process/println-with-broken-pipe.rs -tests/ui/lto/thin-lto-inlines2.rs -tests/ui/panic-runtime/lto-abort.rs -tests/ui/lto/lto-thin-rustc-loads-linker-plugin.rs -tests/ui/async-await/deep-futures-are-freeze.rs -tests/ui/coroutine/resume-after-return.rs -tests/ui/simd/masked-load-store.rs tests/ui/simd/repr_packed.rs -tests/ui/async-await/in-trait/dont-project-to-specializable-projection.rs -tests/ui/coroutine/unwind-abort-mix.rs -tests/ui/consts/issue-miri-1910.rs -tests/ui/consts/const_cmp_type_id.rs -tests/ui/consts/issue-94675.rs -tests/ui/traits/const-traits/const-drop-fail.rs -tests/ui/runtime/on-broken-pipe/child-processes.rs -tests/ui/sanitizer/cfi/assoc-ty-lifetime-issue-123053.rs -tests/ui/sanitizer/cfi/async-closures.rs -tests/ui/sanitizer/cfi/closures.rs -tests/ui/sanitizer/cfi/complex-receiver.rs -tests/ui/sanitizer/cfi/coroutine.rs -tests/ui/sanitizer/cfi/drop-in-place.rs -tests/ui/sanitizer/cfi/drop-no-principal.rs -tests/ui/sanitizer/cfi/fn-ptr.rs -tests/ui/sanitizer/cfi/self-ref.rs -tests/ui/sanitizer/cfi/supertraits.rs -tests/ui/sanitizer/cfi/virtual-auto.rs -tests/ui/sanitizer/cfi/sized-associated-ty.rs -tests/ui/sanitizer/cfi/can-reveal-opaques.rs -tests/ui/sanitizer/kcfi-mangling.rs -tests/ui/delegation/fn-header.rs -tests/ui/consts/const-eval/parse_ints.rs -tests/ui/simd/intrinsic/generic-as.rs -tests/ui/runtime/rt-explody-panic-payloads.rs -tests/ui/codegen/equal-pointers-unequal/as-cast/inline1.rs -tests/ui/codegen/equal-pointers-unequal/as-cast/inline2.rs -tests/ui/codegen/equal-pointers-unequal/as-cast/segfault.rs -tests/ui/codegen/equal-pointers-unequal/as-cast/zero.rs -tests/ui/codegen/equal-pointers-unequal/exposed-provenance/inline1.rs -tests/ui/codegen/equal-pointers-unequal/exposed-provenance/inline2.rs -tests/ui/codegen/equal-pointers-unequal/exposed-provenance/segfault.rs -tests/ui/codegen/equal-pointers-unequal/exposed-provenance/zero.rs -tests/ui/codegen/equal-pointers-unequal/strict-provenance/inline1.rs -tests/ui/codegen/equal-pointers-unequal/strict-provenance/inline2.rs -tests/ui/codegen/equal-pointers-unequal/strict-provenance/segfault.rs -tests/ui/codegen/equal-pointers-unequal/strict-provenance/zero.rs tests/ui/simd/simd-bitmask-notpow2.rs tests/ui/codegen/StackColoring-not-blowup-stack-issue-40883.rs tests/ui/numbers-arithmetic/u128-as-f32.rs tests/ui/process/nofile-limit.rs -tests/ui/linking/no-gc-encapsulation-symbols.rs -tests/ui/panics/unwind-force-no-unwind-tables.rs tests/ui/attributes/fn-align-dyn.rs tests/ui/linkage-attr/raw-dylib/elf/glibc-x86_64.rs -tests/ui/explicit-tail-calls/recursion-etc.rs -tests/ui/explicit-tail-calls/indexer.rs -tests/ui/explicit-tail-calls/drop-order.rs -tests/ui/c-variadic/valid.rs -tests/ui/c-variadic/inherent-method.rs -tests/ui/c-variadic/trait-method.rs -tests/ui/explicit-tail-calls/become-cast-return.rs -tests/ui/explicit-tail-calls/become-indirect-return.rs -tests/ui/panics/panic-abort-backtrace-without-debuginfo.rs -tests/ui/sanitizer/kcfi-c-variadic.rs -tests/ui/sanitizer/kcfi/fn-trait-objects.rs tests/ui/statics/const_generics.rs -tests/ui/test-attrs/test-panic-while-printing.rs tests/ui/thir-print/offset_of.rs -tests/ui/iterators/rangefrom-overflow-debug.rs -tests/ui/iterators/rangefrom-overflow-overflow-checks.rs -tests/ui/iterators/iter-filter-count-debug-check.rs -tests/ui/eii/linking/codegen_single_crate.rs -tests/ui/eii/linking/codegen_cross_crate.rs -tests/ui/eii/default/local_crate.rs -tests/ui/eii/duplicate/multiple_impls.rs -tests/ui/eii/default/call_default.rs -tests/ui/eii/linking/same-symbol.rs -tests/ui/eii/privacy1.rs -tests/ui/eii/default/call_impl.rs -tests/ui/c-variadic/copy.rs tests/ui/asm/x86_64/global_asm_escape.rs tests/ui/lto/all-crates.rs -tests/ui/consts/const-eval/c-variadic.rs -tests/ui/eii/default/call_default_panics.rs -tests/ui/explicit-tail-calls/indirect.rs -tests/ui/traits/inheritance/self-in-supertype.rs -tests/ui/fmt/fmt_debug/shallow.rs -tests/ui/c-variadic/roundtrip.rs -tests/ui/eii/eii_impl_with_contract.rs -tests/ui/eii/static/cross_crate_decl.rs -tests/ui/eii/static/cross_crate_def.rs -tests/ui/eii/static/same_address.rs -tests/ui/eii/static/simple.rs -tests/ui/explicit-tail-calls/default-trait-method.rs +tests/ui/explicit-tail-calls/tailcc-no-signature-restriction.rs +tests/ui/abi/rust-tail-cc.rs +tests/ui/abi/rust-preserve-none-cc.rs +tests/ui/extern/extern-types-field-offset.rs diff --git a/compiler/rustc_codegen_gcc/tests/lang_tests.rs b/compiler/rustc_codegen_gcc/tests/lang_tests.rs index 6afd54e1c3fe0..7ec0ab877b025 100644 --- a/compiler/rustc_codegen_gcc/tests/lang_tests.rs +++ b/compiler/rustc_codegen_gcc/tests/lang_tests.rs @@ -7,6 +7,68 @@ use std::process::Command; use lang_tester::LangTester; use tempfile::TempDir; +/// Directory holding the C files that the `tests/run` tests can link against. +/// +/// A `tests/c/.c` is compiled by the real GCC and linked into `tests/run/.rs`. +const C_TESTS_DIR: &str = "tests/c"; + +/// The m68k cross toolchain is not on the default `PATH` in CI. +// FIXME(antoyo): find a better way to add the PATH necessary locally. +const M68K_TOOLCHAIN_DIR: &str = "/opt/m68k-unknown-linux-gnu/bin"; + +fn target_path(test_target: &Option) -> Option { + test_target.as_ref().map(|_| { + let env_path = std::env::var("PATH").unwrap_or_default(); + format!("{}:{}", M68K_TOOLCHAIN_DIR, env_path) + }) +} + +/// Compile every C file in `tests/c` to an object file in `objects_dir`. +fn compile_c_files(objects_dir: &Path, test_target: &Option) { + let c_tests_dir = Path::new(C_TESTS_DIR); + if !c_tests_dir.is_dir() { + return; + } + std::fs::create_dir_all(objects_dir).expect("create the directory for the C object files"); + + let compiler = match test_target { + Some(target) => format!("{}-gcc", target), + None => "gcc".to_string(), + }; + + for entry in std::fs::read_dir(c_tests_dir).expect("read the C tests directory") { + let source = entry.expect("directory entry").path(); + if source.extension().and_then(|extension| extension.to_str()) != Some("c") { + continue; + } + let object = c_object_path(objects_dir, &source); + + let mut command = Command::new(&compiler); + command.arg("-c"); + // Optimize: an unoptimized C caller can happen to agree with a wrong callee. + command.arg("-O1"); + // GCC notes that the ABI of over-aligned arguments changed in GCC 4.6. That is the ABI + // being tested in overaligned_byval_abi, so the note is expected rather than a problem. + command.arg("-Wno-psabi"); + command.arg("-o"); + command.arg(&object); + command.arg(&source); + if let Some(env_path) = target_path(test_target) { + command.env("PATH", env_path); + } + + let status = command + .status() + .unwrap_or_else(|error| panic!("failed to run `{}`: {}", compiler, error)); + assert!(status.success(), "failed to compile `{}`", source.display()); + } +} + +/// The object file that a test source links against, if any: `tests/c/x.c` for `tests/run/x.rs`. +fn c_object_path(objects_dir: &Path, source: &Path) -> PathBuf { + objects_dir.join(source.file_stem().expect("file_stem")).with_extension("o") +} + fn compile_and_run_cmds( compiler_args: Vec, test_target: &Option, @@ -18,10 +80,7 @@ fn compile_and_run_cmds( // Test command 2: run `tempdir/x`. if test_target.is_some() { - let mut env_path = std::env::var("PATH").unwrap_or_default(); - // FIXME(antoyo): find a better way to add the PATH necessary locally. - env_path = format!("/opt/m68k-unknown-linux-gnu/bin:{}", env_path); - compiler.env("PATH", env_path); + compiler.env("PATH", target_path(test_target).expect("target PATH")); let mut commands = vec![("Compiler", compiler)]; if test_mode.should_run() { @@ -82,8 +141,10 @@ impl TestMode { } } +#[allow(clippy::too_many_arguments)] fn build_test_runner( tempdir: PathBuf, + c_objects_dir: PathBuf, current_dir: String, build_mode: BuildMode, test_kind: &str, @@ -159,6 +220,13 @@ fn build_test_runner( path.to_str().expect("to_str").into(), ]; + // Link against `tests/c/.c`, when the test has one. + let c_object = c_object_path(&c_objects_dir, path); + if c_object.exists() { + compiler_args.push("-C".into()); + compiler_args.push(format!("link-arg={}", c_object.display())); + } + if let Some(ref target) = test_target { compiler_args.extend_from_slice(&["--target".into(), target.into()]); @@ -172,6 +240,16 @@ fn build_test_runner( } } + // Extra flags passed at run time (as opposed to the compile-time + // `TEST_FLAGS`). This lets a single test opt into flags like + // `-Zmir-preserve-ub` via an `ignore-if` directive that checks + // whether `CARGO_TEST_FLAGS` is set. + if let Ok(flags) = std::env::var("CARGO_TEST_FLAGS") { + for flag in flags.split_whitespace() { + compiler_args.push(flag.into()); + } + } + if build_mode.is_debug() { compiler_args .extend_from_slice(&["-C".to_string(), "llvm-args=sanitize-undefined".into()]); @@ -193,36 +271,51 @@ fn build_test_runner( .run(); } -fn compile_tests(tempdir: PathBuf, current_dir: String) { +fn compile_tests(tempdir: PathBuf, c_objects_dir: PathBuf, current_dir: String) { build_test_runner( tempdir, + c_objects_dir, current_dir, BuildMode::Debug, "lang compile", "tests/compile", TestMode::Compile, - &["simd-ffi.rs", "asm_nul_byte.rs", "global_asm_nul_byte.rs", "naked_asm_nul_byte.rs"], + &[ + "simd-ffi.rs", + "asm_nul_byte.rs", + "global_asm_nul_byte.rs", + "naked_asm_nul_byte.rs", + "x86_interrupt_first_arg_byval.rs", + ], ); } -fn run_tests(tempdir: PathBuf, current_dir: String) { +fn run_tests(tempdir: PathBuf, c_objects_dir: PathBuf, current_dir: String) { build_test_runner( tempdir.clone(), + c_objects_dir.clone(), current_dir.clone(), BuildMode::Debug, "[DEBUG] lang run", "tests/run", TestMode::CompileAndRun, - &[], + &[ + // FIXME: remove this when the unwind issue is fixed in GCC m68k upstream. + "catch_unwind.rs", + ], ); build_test_runner( tempdir, + c_objects_dir, current_dir.to_string(), BuildMode::Release, "[RELEASE] lang run", "tests/run", TestMode::CompileAndRun, - &[], + &[ + // FIXME: remove this when the unwind issue is fixed in GCC m68k upstream. + "catch_unwind.rs", + ], ); } @@ -232,6 +325,11 @@ fn main() { let current_dir = current_dir.to_str().expect("current dir").to_string(); let tempdir_path: PathBuf = tempdir.as_ref().into(); - compile_tests(tempdir_path.clone(), current_dir.clone()); - run_tests(tempdir_path, current_dir); + let c_objects_dir = tempdir_path.join("c-objects"); + // FIXME(antoyo): find a way to send this via a cli argument. + let test_target = std::env::var("CG_GCC_TEST_TARGET").ok(); + compile_c_files(&c_objects_dir, &test_target); + + compile_tests(tempdir_path.clone(), c_objects_dir.clone(), current_dir.clone()); + run_tests(tempdir_path, c_objects_dir, current_dir); } diff --git a/compiler/rustc_codegen_gcc/tests/run/asm.rs b/compiler/rustc_codegen_gcc/tests/run/asm.rs index 01775c92ffc8a..42141c671b596 100644 --- a/compiler/rustc_codegen_gcc/tests/run/asm.rs +++ b/compiler/rustc_codegen_gcc/tests/run/asm.rs @@ -3,6 +3,8 @@ // Run-time: // status: 0 +#![feature(asm_goto_with_outputs)] + #[cfg(target_arch = "x86_64")] use std::arch::{asm, global_asm}; @@ -32,6 +34,20 @@ pub unsafe fn mem_cpy(dst: *mut u8, src: *const u8, len: usize) { ); } +#[cfg(target_arch = "x86_64")] +#[unsafe(no_mangle)] +pub fn asm_goto_test(mut a: i16) -> i16 { + unsafe { + std::arch::asm!( + "jmp {op}", + inout("eax") a, + op = label { a = 7; }, + options(nostack,nomem) + ); + a + } +} + #[cfg(target_arch = "x86_64")] fn asm() { unsafe { @@ -190,6 +206,14 @@ fn asm() { } assert_eq!((x, y), (8, 8)); + // Regression test for + // typed pointer inputs to explicit registers need a cast. + let mut x = 123_i32; + unsafe { + asm!("", in("rdi") &mut x, options(nostack, preserves_flags)); + } + assert_eq!(x, 123); + // sysv64 is the default calling convention on unix systems. The rdi register is // used to pass arguments in the sysv64 calling convention, so this register will be clobbered #[cfg(unix)] @@ -227,6 +251,24 @@ fn asm() { out("r15b") _, ); } + + // Make sure the input value from inout is assigned to the input value + unsafe { + // Use a very distinctive value unlikely to live in any register. + let input: u64 = 0x1234567890ABCDEF; + let mut output: u64; + + asm!( + "push {1}", + "pop {0}", + out(reg) output, + inout(reg) input => _, + ); + + assert_eq!(output, 0x1234567890ABCDEF); + } + + asm_goto_test(0); } #[cfg(not(target_arch = "x86_64"))] diff --git a/compiler/rustc_codegen_gcc/tests/run/catch_unwind.rs b/compiler/rustc_codegen_gcc/tests/run/catch_unwind.rs new file mode 100644 index 0000000000000..919213db5a4a5 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/catch_unwind.rs @@ -0,0 +1,25 @@ +// Compiler: +// +// Run-time: +// status: 0 +// stdout: Caught + +#![feature(fn_traits, unboxed_closures)] + +struct Wrapper(A); + +impl R> FnOnce<()> for Wrapper { + type Output = R; + + #[inline] + extern "rust-call" fn call_once(self, _args: ()) -> R { + (self.0)() + } +} + +fn main() { + std::panic::set_hook(Box::new(|_| {})); + let result = std::panic::catch_unwind(Wrapper(|| panic!())); + assert!(result.is_err()); + println!("Caught"); +} diff --git a/compiler/rustc_codegen_gcc/tests/run/custom_abort.rs b/compiler/rustc_codegen_gcc/tests/run/custom_abort.rs new file mode 100644 index 0000000000000..eafa4321a4324 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/custom_abort.rs @@ -0,0 +1,27 @@ +// Compiler: +// +// Run-time: +// status: 42 + +// Check that a program can define its own `abort`. + +#![feature(no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use mini_core::*; + +#[no_mangle] +extern "C" fn abort() { + unsafe { + libc::exit(42); + } +} + +#[no_mangle] +extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + abort(); + 0 +} diff --git a/compiler/rustc_codegen_gcc/tests/run/import_linkage.rs b/compiler/rustc_codegen_gcc/tests/run/import_linkage.rs new file mode 100644 index 0000000000000..bf5cb9e532799 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/import_linkage.rs @@ -0,0 +1,84 @@ +// Compiler: +// +// Run-time: +// status: 0 + +// Checks the `#[linkage]` flavours an `extern` static can be imported with, against the symbols +// `tests/c/import_linkage.c` defines. +// +// The value of such an import is the address of the symbol rather than its contents, which is why +// the types are pointers: an `extern_weak` import of a symbol nobody defines reads as null instead +// of failing the link. + +#![feature(linkage, no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use mini_core::*; + +extern "C" { + #[linkage = "external"] + static external_value: *const i32; + #[linkage = "available_externally"] + static available_externally_value: *const i32; + #[linkage = "linkonce"] + static linkonce_value: *const i32; + #[linkage = "linkonce_odr"] + static linkonce_odr_value: *const i32; + #[linkage = "weak"] + static weak_value: *const i32; + #[linkage = "weak_odr"] + static weak_odr_value: *const i32; + #[linkage = "common"] + static common_value: *const i32; + #[linkage = "extern_weak"] + static extern_weak_value: *const i32; + // An import is an undefined reference whatever the flavour says. Upstream bug: rustc lowers + // this one to an internal declaration, which LLVM's verifier rejects ("Global is external, but + // doesn't have external or weak linkage!") and which crashes cg_llvm at -O3. + #[linkage = "internal"] + static internal_value: *const i32; + + // Nothing defines this one, so it stays null instead of breaking the link. + #[linkage = "extern_weak"] + static undefined_value: *const i32; +} + +#[no_mangle] +extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + unsafe { + if *external_value != 1 { + return 1; + } + if *available_externally_value != 2 { + return 2; + } + if *linkonce_value != 3 { + return 3; + } + if *linkonce_odr_value != 4 { + return 4; + } + if *weak_value != 5 { + return 5; + } + if *weak_odr_value != 6 { + return 6; + } + if *common_value != 7 { + return 7; + } + if *extern_weak_value != 8 { + return 8; + } + if *internal_value != 9 { + return 9; + } + if undefined_value as usize != 0 { + return 10; + } + } + 0 +} diff --git a/compiler/rustc_codegen_gcc/tests/run/int.rs b/compiler/rustc_codegen_gcc/tests/run/int.rs index 78675acb5447b..ef825b4d80185 100644 --- a/compiler/rustc_codegen_gcc/tests/run/int.rs +++ b/compiler/rustc_codegen_gcc/tests/run/int.rs @@ -319,4 +319,29 @@ fn main() { const VAL5: T = 73236519889708027473620326106273939584_i128; check_ops128!(); } + + { + #[allow(dead_code)] + #[repr(u8)] + enum Inner { + L0 = 0, + H255 = 255, + } + #[allow(dead_code)] + enum O { + A(Inner), + B, + C, + } + + #[inline(never)] + fn which(o: &O) -> &'static str { + match o { + O::A(_) => "a", + O::B => "b", + O::C => "c", + } + } + assert_eq!(which(black_box(&O::A(Inner::H255))), "a"); + } } diff --git a/compiler/rustc_codegen_gcc/tests/run/mir_preserve_ub_empty_switch.rs b/compiler/rustc_codegen_gcc/tests/run/mir_preserve_ub_empty_switch.rs new file mode 100644 index 0000000000000..26056360b9212 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/mir_preserve_ub_empty_switch.rs @@ -0,0 +1,35 @@ +// ignore-if: test -z "$CARGO_TEST_FLAGS" +// Compiler: +// +// Run-time: +// status: 0 + +// Regression test for https://github.com/rust-lang/rustc_codegen_gcc/issues/881 +// +// This needs `-Zmir-preserve-ub`, so it is skipped unless that flag is passed +// through `CARGO_TEST_FLAGS` (see the `ignore-if` directive above). Run it with: +// CARGO_TEST_FLAGS="-Zmir-preserve-ub" ./y.sh test --cargo-tests -- mir_preserve_ub_empty_switch + +#![feature(no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use intrinsics::black_box; +use mini_core::*; + +#[no_mangle] +extern "C" fn main(argc: i32, _argv: *const *const u8) -> i32 { + // With `-Zmir-preserve-ub`, the range pattern below is lowered to a pair of + // comparisons and the second one becomes a `SwitchInt` with no cases (only + // an `otherwise` target) whose discriminant is the `bool` comparison + // result. `gcc_jit_block_end_with_switch` rejects a non-integer + // discriminant, so the backend must emit a plain jump for it instead. + let value = black_box(argc); + match value { + 0..=9 => (), + _ => (), + } + 0 +} diff --git a/compiler/rustc_codegen_gcc/tests/run/overaligned_byval_abi.rs b/compiler/rustc_codegen_gcc/tests/run/overaligned_byval_abi.rs new file mode 100644 index 0000000000000..78ee35f05ca58 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/overaligned_byval_abi.rs @@ -0,0 +1,89 @@ +// Compiler: +// +// Run-time: +// status: 0 + +// Checks that cg_gcc passes an over-aligned by-value ("byval") argument where the platform ABI +// says it goes, by calling in both directions with `tests/c/overaligned_byval_abi.c`, which is +// compiled by the real GCC. +// +// `tests/run/overaligned_byval_arg.rs` covers the Rust-visible half of the same bug. It cannot +// cover this one: with cg_gcc on both sides of a call, caller and callee place the argument at +// the same wrong offset and agree with each other. +// +// Two over-aligned arguments are used rather than one so that the failure is deterministic. A +// backend that drops `align(64)` packs the arguments at offsets 0, 24, 88 and 112 of the argument +// area; 112 - 24 = 88 is not a multiple of 64, so the two of them cannot both land on a 64-byte +// boundary however the argument area itself is aligned. With a single over-aligned argument the +// frame often happens to be 64-aligned and the bug hides. +// +// Only the values received are checked, never the address an argument landed at: which alignment +// a target gives a by-value stack slot differs between targets, but the two sides of a call +// agreeing on it does not. `overaligned_byval_arg.rs` is where the alignment itself is asserted. + +#![feature(no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use mini_core::*; + +#[repr(C)] +struct Big { + a: i64, + b: i64, + c: i64, +} + +#[repr(C, align(64))] +struct Aligned { + x: i32, +} + +extern "C" { + fn c_take_both(first: Big, second: Aligned, third: Big, fourth: Aligned) -> i32; + fn c_call_rust() -> i32; +} + +// The callee for the GCC-built caller in `c_call_rust`. +// +// `#[no_mangle]` is not only about the symbol name: it makes the symbol externally visible, which +// pins the calling convention. Without it the function has internal linkage and GCC is free to +// clone it with a changed convention at `-O3` (the symbol comes out as `...constprop.0.isra.0`), +// so the arguments never travel through the stack slots and the release build passes spuriously. +#[no_mangle] +extern "C" fn rust_take_both(first: Big, second: Aligned, third: Big, fourth: Aligned) -> i32 { + if first.a as i32 != 1 || first.b as i32 != 2 || first.c as i32 != 3 { + return 5; + } + if second.x != 42 { + return 6; + } + if third.a as i32 != 4 || third.b as i32 != 5 || third.c as i32 != 6 { + return 7; + } + if fourth.x != 43 { + return 8; + } + 0 +} + +#[no_mangle] +extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + // cg_gcc as the caller, GCC as the callee. + let result = unsafe { + c_take_both( + Big { a: 1, b: 2, c: 3 }, + Aligned { x: 42 }, + Big { a: 4, b: 5, c: 6 }, + Aligned { x: 43 }, + ) + }; + if result != 0 { + return result; + } + + // GCC as the caller, cg_gcc as the callee. + unsafe { c_call_rust() } +} diff --git a/compiler/rustc_codegen_gcc/tests/run/overaligned_byval_arg.rs b/compiler/rustc_codegen_gcc/tests/run/overaligned_byval_arg.rs new file mode 100644 index 0000000000000..e20ec11732962 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/overaligned_byval_arg.rs @@ -0,0 +1,41 @@ +// Compiler: +// +// Run-time: +// status: 0 + +#![feature(no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use mini_core::*; + +#[repr(C)] +struct Big { + a: i64, + b: i64, + c: i64, +} + +#[repr(C, align(64))] +struct Aligned { + x: i32, +} + +#[inline(never)] +#[no_mangle] +extern "C" fn check(_b1: Big, a1: Aligned, _b2: Big, a2: Aligned) -> i32 { + if (&a1 as *const Aligned as usize) % 64 != 0 { + return 1; + } + if (&a2 as *const Aligned as usize) % 64 != 0 { + return 2; + } + 0 +} + +#[no_mangle] +extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + check(Big { a: 1, b: 2, c: 3 }, Aligned { x: 42 }, Big { a: 4, b: 5, c: 6 }, Aligned { x: 43 }) +} diff --git a/compiler/rustc_codegen_gcc/tests/run/ptr_to_int_div.rs b/compiler/rustc_codegen_gcc/tests/run/ptr_to_int_div.rs new file mode 100644 index 0000000000000..afc563d6c977b --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/ptr_to_int_div.rs @@ -0,0 +1,19 @@ +// Compiler: +// +// Run-time: +// status: 0 + +use std::hint::black_box; +use std::mem::transmute; + +fn main() { + let pointer = black_box(usize::MAX) as *const (); + + let unsigned = unsafe { transmute::<*const (), usize>(pointer) }; + assert_eq!(unsigned / black_box(2), usize::MAX / 2); + assert_eq!(unsigned % black_box(2), usize::MAX % 2); + + let signed = unsafe { transmute::<*const (), isize>(pointer) }; + assert_eq!(signed / black_box(2), -1isize / 2); + assert_eq!(signed % black_box(2), -1isize % 2); +} diff --git a/compiler/rustc_codegen_gcc/tests/run/simd.rs b/compiler/rustc_codegen_gcc/tests/run/simd.rs new file mode 100644 index 0000000000000..e0a23fdccf8ce --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/simd.rs @@ -0,0 +1,81 @@ +// Compiler: +// +// Run-time: +// status: 0 + +#![feature(portable_simd)] + +use std::hint::black_box; +use std::simd::prelude::*; + +fn test_saturating_add() { + let values = i32x4::from_array([i32::MIN, -2, 3, i32::MAX]); + let ones = i32x4::splat(1); + assert_eq!( + black_box(values).saturating_add(black_box(ones)).to_array(), + [i32::MIN + 1, -1, 4, i32::MAX] + ); + + let values = u32x4::from_array([0, 2, 3, u32::MAX]); + let ones = u32x4::splat(1); + assert_eq!(black_box(values).saturating_add(black_box(ones)).to_array(), [1, 3, 4, u32::MAX]); +} + +fn test_saturating_sub() { + let values = i32x4::from_array([i32::MIN, -2, 3, i32::MAX]); + let zero = i32x4::splat(0); + assert_eq!( + black_box(zero).saturating_sub(black_box(values)).to_array(), + [i32::MAX, 2, -3, i32::MIN + 1] + ); + assert_eq!(black_box(values).saturating_neg().to_array(), [i32::MAX, 2, -3, i32::MIN + 1]); + assert_eq!(black_box(values).saturating_abs().to_array(), [i32::MAX, 2, 3, i32::MAX]); + + let values = i32x4::from_array([i32::MIN, -2, 3, i32::MAX]); + let ones = i32x4::splat(1); + assert_eq!( + black_box(values).saturating_sub(black_box(ones)).to_array(), + [i32::MIN, -3, 2, i32::MAX - 1] + ); + + let values = u32x4::from_array([0, 2, 3, u32::MAX]); + let ones = u32x4::splat(1); + assert_eq!( + black_box(values).saturating_sub(black_box(ones)).to_array(), + [0, 1, 2, u32::MAX - 1] + ); +} + +fn test_float_cast() { + let floats = f32x4::from_array([1.9, -4.5, f32::INFINITY, f32::NAN]); + assert_eq!(black_box(floats).cast::().to_array(), [1, -4, i32::MAX, 0]); + + let floats = f32x4::from_array([f32::NEG_INFINITY, 1e20, -1e20, -0.0]); + assert_eq!(black_box(floats).cast::().to_array(), [i32::MIN, i32::MAX, i32::MIN, 0]); + + let floats = f32x4::from_array([-1.0, 3.7, f32::NAN, 1e20]); + assert_eq!(black_box(floats).cast::().to_array(), [0, 3, 0, u32::MAX]); + + let floats = f64x4::from_array([-1.5, 2.5, f64::NAN, f64::INFINITY]); + assert_eq!(black_box(floats).cast::().to_array(), [-1, 2, 0, i64::MAX]); +} + +fn test_arith_offset() { + let values = [10i32, 11, 12, 13, 14, 15, 16, 17]; + let indices = usizex4::from_array([7, 5, 3, 1]); + assert_eq!( + i32x4::gather_or_default(black_box(&values), black_box(indices)).to_array(), + [17, 15, 13, 11] + ); + + let mut destination = [0i32; 8]; + i32x4::from_array([1, 2, 3, 4]).scatter(black_box(&mut destination), black_box(indices)); + assert_eq!(destination, [0, 4, 0, 3, 0, 2, 0, 1]); +} + +fn main() { + test_saturating_add(); + test_saturating_sub(); + test_float_cast(); + test_arith_offset(); +} diff --git a/compiler/rustc_codegen_gcc/tests/run/static_alloc_shapes.rs b/compiler/rustc_codegen_gcc/tests/run/static_alloc_shapes.rs new file mode 100644 index 0000000000000..39a4d07bdf638 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/static_alloc_shapes.rs @@ -0,0 +1,50 @@ +// Compiler: +// +// Run-time: +// status: 0 +// stdout: 8 +// 12 +// 5 +// 7 +// 7 +// 9 + +#![feature(no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use mini_core::*; + +// One byte run of each length class that maps to a distinct array element type. +static mut BYTES8: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8]; +static mut BYTES12: [u8; 12] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; +static mut BYTES5: [u8; 5] = [1, 2, 3, 4, 5]; + +static mut VALUE: isize = 7; +static mut OTHER: isize = 9; + +// An allocation that is exactly one relocation, so it ends on a pointer with no trailing bytes. +static mut PTR: &isize = unsafe { &VALUE }; + +struct TwoRefs { + first: &'static isize, + second: &'static isize, +} + +// Two adjacent relocations, with no byte run between them. +static mut TWO_REFS: TwoRefs = TwoRefs { first: unsafe { &VALUE }, second: unsafe { &OTHER } }; + +#[no_mangle] +extern "C" fn main(_argc: isize, _argv: *const *const u8) -> i32 { + unsafe { + libc::printf(b"%ld\n\0" as *const u8 as *const i8, BYTES8[7] as isize); + libc::printf(b"%ld\n\0" as *const u8 as *const i8, BYTES12[11] as isize); + libc::printf(b"%ld\n\0" as *const u8 as *const i8, BYTES5[4] as isize); + libc::printf(b"%ld\n\0" as *const u8 as *const i8, *PTR); + libc::printf(b"%ld\n\0" as *const u8 as *const i8, *TWO_REFS.first); + libc::printf(b"%ld\n\0" as *const u8 as *const i8, *TWO_REFS.second); + } + 0 +} diff --git a/compiler/rustc_codegen_gcc/tests/run/static_linkage.rs b/compiler/rustc_codegen_gcc/tests/run/static_linkage.rs new file mode 100644 index 0000000000000..7b911c064d797 --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/static_linkage.rs @@ -0,0 +1,79 @@ +// Compiler: +// +// Run-time: +// status: 0 + +// Checks that `#[linkage]` on a static that this crate defines reaches the symbol, against +// `tests/c/static_linkage.c`, which defines the overridable ones strongly. +// +// If `predefine_static` were to ignore its `linkage` argument outright, every static would come out as +// an ordinary global symbol: the overridable ones would clash with the C definitions at link time, and +// `internal` would export a symbol it should have kept private. + +#![feature(linkage, no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use mini_core::*; + +#[linkage = "weak"] +#[no_mangle] +pub static weak_static: i32 = 0; + +#[linkage = "weak_odr"] +#[no_mangle] +pub static weak_odr_static: i32 = 0; + +#[linkage = "linkonce"] +#[no_mangle] +pub static linkonce_static: i32 = 0; + +#[linkage = "linkonce_odr"] +#[no_mangle] +pub static linkonce_odr_static: i32 = 0; + +// `common` is only valid on a mutable global: LLVM rejects a constant one. +#[linkage = "common"] +#[no_mangle] +pub static mut common_static: i32 = 0; + +// Private to this crate, so the C definition of the same name is a different object. +#[linkage = "internal"] +#[no_mangle] +pub static internal_static: i32 = 100; + +// Not overridden by the C side: the definition here is the one that survives. +#[linkage = "weak"] +#[no_mangle] +pub static only_weak_static: i32 = 6; + +// The real definition is the one in the C file; a backend may read it or emit an equivalent copy of +// this initializer, so both spell the same value. +#[linkage = "available_externally"] +#[no_mangle] +pub static available_externally_static: i32 = 7; + +extern "C" { + fn c_read_all() -> i32; +} + +#[no_mangle] +extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + let result = unsafe { c_read_all() }; + if result != 0 { + return result; + } + + if internal_static != 100 { + return 1; + } + if only_weak_static != 6 { + return 2; + } + if available_externally_static != 7 { + return 3; + } + 0 +} diff --git a/compiler/rustc_codegen_gcc/tests/run/weak_function_linkage.rs b/compiler/rustc_codegen_gcc/tests/run/weak_function_linkage.rs new file mode 100644 index 0000000000000..677f01353401a --- /dev/null +++ b/compiler/rustc_codegen_gcc/tests/run/weak_function_linkage.rs @@ -0,0 +1,106 @@ +// Compiler: +// +// Run-time: +// status: 0 + +// Checks that the `#[linkage]` flavours another object file is allowed to override are emitted as +// weak symbols, by linking against `tests/c/weak_function_linkage.c`, which defines the same +// symbols strongly. + +#![feature(linkage, no_core)] +#![no_std] +#![no_core] +#![no_main] + +extern crate mini_core; +use mini_core::*; + +#[linkage = "weak"] +#[no_mangle] +extern "C" fn weak_function() -> i32 { + 0 +} + +// `_odr` promises every definition of the symbol is equivalent, which lets a backend call this body +// instead of the one in the C file. They spell the same value for that reason. +#[linkage = "weak_odr"] +#[no_mangle] +extern "C" fn weak_odr_function() -> i32 { + 2 +} + +#[linkage = "linkonce"] +#[no_mangle] +extern "C" fn linkonce_function() -> i32 { + 0 +} + +#[linkage = "linkonce_odr"] +#[no_mangle] +extern "C" fn linkonce_odr_function() -> i32 { + 4 +} + +// `#[linkage = "common"]` is absent on purpose: a common symbol is `SHN_COMMON`, which the object +// format only allows for objects, so no backend can give a function that linkage. + +// Not overridden by the C side: the definition here is the one that runs. +#[linkage = "weak"] +#[no_mangle] +extern "C" fn only_weak_function() -> i32 { + 6 +} + +// The real definition is the one in the C file; a backend may call it or emit an equivalent copy of +// this body, so both spell the same value. +#[linkage = "available_externally"] +#[no_mangle] +extern "C" fn available_externally_function() -> i32 { + 7 +} + +// GCC drops `weak` from a function that is also `inline`: a backend that keeps the hint emits this +// as an ordinary global symbol and clashes with the C definition. rustc lints the hint as ignored +// on a function with an explicit `#[linkage]`, hence the `allow`. +#[linkage = "weak"] +#[inline] +#[no_mangle] +#[allow(unused_attributes)] +extern "C" fn weak_inline_function() -> i32 { + 0 +} + +extern "C" { + fn c_call_all() -> i32; +} + +#[no_mangle] +extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 { + let result = unsafe { c_call_all() }; + if result != 0 { + return result; + } + + if weak_function() != 1 { + return 1; + } + if weak_odr_function() != 2 { + return 2; + } + if linkonce_function() != 3 { + return 3; + } + if linkonce_odr_function() != 4 { + return 4; + } + if only_weak_function() != 6 { + return 6; + } + if available_externally_function() != 7 { + return 7; + } + if weak_inline_function() != 8 { + return 8; + } + 0 +} diff --git a/compiler/rustc_codegen_gcc/tools/cspell_dicts/rust.txt b/compiler/rustc_codegen_gcc/tools/cspell_dicts/rust.txt deleted file mode 100644 index 379cbd77eef01..0000000000000 --- a/compiler/rustc_codegen_gcc/tools/cspell_dicts/rust.txt +++ /dev/null @@ -1,2 +0,0 @@ -lateout -repr diff --git a/compiler/rustc_codegen_gcc/tools/cspell_dicts/rustc_codegen_gcc.txt b/compiler/rustc_codegen_gcc/tools/cspell_dicts/rustc_codegen_gcc.txt deleted file mode 100644 index 4fb018b3ecd87..0000000000000 --- a/compiler/rustc_codegen_gcc/tools/cspell_dicts/rustc_codegen_gcc.txt +++ /dev/null @@ -1,78 +0,0 @@ -aapcs -addo -archs -ashl -ashr -cgcx -clzll -cmse -codegened -csky -ctfe -ctlz -ctpop -cttz -ctzll -flto -fmaximumf -fmuladd -fmuladdf -fminimumf -fmul -fptosi -fptosui -fptoui -fwrapv -gimple -hrtb -immediates -interner -liblto -llbb -llcx -llextra -llfn -lgcc -llmod -llresult -llret -ltrans -llty -llval -llvals -loong -lshr -masm -maximumf -maxnumf -mavx -mcmodel -minimumf -minnumf -miri -monomorphization -monomorphizations -monomorphized -monomorphizing -movnt -mulo -nvptx -pointee -powitf -reassoc -riscv -rlib -roundevenf -rustc -sitofp -sizet -spir -subo -sysv -tbaa -uitofp -unord -uninlined -utrunc -xabort -zext diff --git a/compiler/rustc_codegen_gcc/tools/generate_intrinsics.py b/compiler/rustc_codegen_gcc/tools/generate_intrinsics.py index 5390323407779..06425f682a88b 100644 --- a/compiler/rustc_codegen_gcc/tools/generate_intrinsics.py +++ b/compiler/rustc_codegen_gcc/tools/generate_intrinsics.py @@ -84,6 +84,10 @@ def update_intrinsics(llvm_path): # This speeds up the comparison, and makes our code considerably smaller. # Since all intrinsic names start with "llvm.", we skip that prefix. print("Updating content of `{}`...".format(output_file)) + indent4 = " " + indent8 = indent4 + indent4 + indent12 = indent8 + indent4 + indent16 = indent12 + indent4 with open(output_file, "w", encoding="utf8") as out: out.write("""// File generated by `rustc_codegen_gcc/tools/generate_intrinsics.py` // DO NOT EDIT IT! @@ -95,33 +99,35 @@ def update_intrinsics(llvm_path): if let ArchCheckResult::Ok(res) = old_arch_res { return res; } -match arch {""") + match arch { +""") for arch in archs: if len(intrinsics[arch]) == 0: continue attribute = "#[expect(non_snake_case)]" if arch[0].isupper() else "" - out.write("\"{}\" => {{ {} fn {}(name: &str,full_name:&str) -> &'static str {{ match name {{".format(arch, attribute, arch)) + out.write(f"""{indent4}"{arch}" => {{ +{indent8}{attribute} fn {arch}(name: &str,full_name:&str) -> &'static str {{ +{indent12}match name {{""") intrinsics[arch].sort(key=lambda x: (x[0], x[1])) - out.write(' // {}\n'.format(arch)) + out.write(f'{indent16}// {arch}\n') for entry in intrinsics[arch]: llvm_name = entry[0].removeprefix("llvm."); llvm_name = llvm_name.removeprefix(arch); llvm_name = llvm_name.removeprefix("."); if "_round_mask" in entry[1]: - out.write(' // [INVALID CONVERSION]: "{}" => "{}",\n'.format(llvm_name, entry[1])) + out.write(f'{indent16}// [INVALID CONVERSION]: "{llvm_name}" => "{entry[1]}",\n') else: - out.write(' "{}" => "{}",\n'.format(llvm_name, entry[1])) - out.write(' _ => unimplemented!("***** unsupported LLVM intrinsic {full_name}"),\n') - out.write("}} }} {}(name,full_name) }}\n,".format(arch)) - out.write(""" _ => { - match old_arch_res { - ArchCheckResult::UnknownIntrinsic => unimplemented!("***** unsupported LLVM intrinsic {full_name}"), - ArchCheckResult::UnknownArch => unimplemented!("***** unsupported LLVM architecture {arch}, intrinsic: {full_name}"), - ArchCheckResult::Ok(_) => unreachable!(), - } - }""") + out.write(f'{indent16}"{llvm_name}" => "{entry[1]}",\n') + out.write(f'{indent16}_ => unimplemented!("***** unsupported LLVM intrinsic {{full_name}}"),\n') + out.write(f"{indent16}}}\n{indent12}}}\n{indent8}{arch}(name,full_name)\n{indent8}}}\n,") + out.write(f"""{indent4}_ => {{ +{indent8}match old_arch_res {{ +{indent8}ArchCheckResult::UnknownIntrinsic => unimplemented!("***** unsupported LLVM intrinsic {{full_name}}"), +{indent8}ArchCheckResult::UnknownArch => unimplemented!("***** unsupported LLVM architecture {{arch}}, intrinsic: {{full_name}}"), +{indent8}ArchCheckResult::Ok(_) => unreachable!(), +{indent4}}} +}}""") out.write("}\n}") - subprocess.call(["rustfmt", output_file]) print("Done!") diff --git a/src/gcc b/src/gcc index 6f155cc3f5a2d..badf78d09d16e 160000 --- a/src/gcc +++ b/src/gcc @@ -1 +1 @@ -Subproject commit 6f155cc3f5a2dff33afe6cc3ed6c2e0e605ae6a3 +Subproject commit badf78d09d16e66f4ca07971c51aa6a227558d4f diff --git a/tests/run-make/comment-section/rmake.rs b/tests/run-make/comment-section/rmake.rs index ccfc38e870d88..2fb768cd82785 100644 --- a/tests/run-make/comment-section/rmake.rs +++ b/tests/run-make/comment-section/rmake.rs @@ -6,6 +6,8 @@ //@ only-linux // FIXME(jieyouxu): check cross-compile setup //@ ignore-cross-compile +// FIXME: Remove once this is fixed in cg_gcc +//@ ignore-backends: gcc use run_make_support::{cwd, env_var, llvm_readobj, rfs, rustc}; diff --git a/tests/run-make/dirty-incr-due-to-hard-link/rmake.rs b/tests/run-make/dirty-incr-due-to-hard-link/rmake.rs index 942b667814a91..91454feea3af4 100644 --- a/tests/run-make/dirty-incr-due-to-hard-link/rmake.rs +++ b/tests/run-make/dirty-incr-due-to-hard-link/rmake.rs @@ -1,4 +1,6 @@ //@ only-x86_64-unknown-linux-gnu +// FIXME: Remove once this is fixed in cg_gcc +//@ ignore-backends: gcc // Regression test for the incremental bug in . // diff --git a/tests/run-make/emit/rmake.rs b/tests/run-make/emit/rmake.rs index 8b3ddb66f9238..541d3237d554a 100644 --- a/tests/run-make/emit/rmake.rs +++ b/tests/run-make/emit/rmake.rs @@ -4,6 +4,8 @@ // See https://github.com/rust-lang/rust/pull/30452 //@ ignore-cross-compile +// FIXME: Remove once this is fixed in cg_gcc +//@ ignore-backends: gcc use run_make_support::{run, rustc}; diff --git a/tests/run-make/extra-filename-with-temp-outputs/rmake.rs b/tests/run-make/extra-filename-with-temp-outputs/rmake.rs index f93a3ecc8d1b5..a012187b2f4d4 100644 --- a/tests/run-make/extra-filename-with-temp-outputs/rmake.rs +++ b/tests/run-make/extra-filename-with-temp-outputs/rmake.rs @@ -7,6 +7,8 @@ // See https://github.com/rust-lang/rust/pull/15686 //@ ignore-cross-compile (relocations in generic ELF against `arm-unknown-linux-gnueabihf`) +// FIXME: Remove once this is fixed in cg_gcc +//@ ignore-backends: gcc use run_make_support::{bin_name, cwd, has_prefix, has_suffix, rfs, rustc, shallow_find_files}; diff --git a/tests/run-make/parallel-reproducible-inline-asm-cookie/rmake.rs b/tests/run-make/parallel-reproducible-inline-asm-cookie/rmake.rs index 62ad5a46af860..33fab7cd18265 100644 --- a/tests/run-make/parallel-reproducible-inline-asm-cookie/rmake.rs +++ b/tests/run-make/parallel-reproducible-inline-asm-cookie/rmake.rs @@ -2,6 +2,8 @@ //@ ignore-cross-compile //@ ignore-windows-gnu // GNU Linker for Windows is non-deterministic. (from `reproducible-build-2` test in this suite) +// FIXME: Remove once this is fixed in cg_gcc +//@ ignore-backends: gcc use std::rc::Rc;