From d865ac7edc44b73722f60d602ca65b6bd8e3f9cb Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Fri, 29 May 2026 13:30:41 -0300 Subject: [PATCH 1/4] Emit a plain jump for a switch with no cases A `SwitchInt` with only an `otherwise` target reaches the backend as a switch with an empty case list. `gcc_jit_block_end_with_switch` requires an integer discriminant, so it rejects the `bool` discriminant produced by a range-pattern comparison under `-Zmir-preserve-ub`, which keeps the otherwise-simplified switch instead of lowering it to a `goto`. Such a switch is equivalent to an unconditional jump to the default block, so emit that instead. --- src/builder.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/builder.rs b/src/builder.rs index 33f0f6fc2f8..8d047dcfa5a 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -570,6 +570,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. From 60006c311dd5204b6b3b66728f3b39540e7b9d80 Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Fri, 29 May 2026 16:04:37 -0300 Subject: [PATCH 2/4] Add CARGO_TEST_FLAGS for run-time lang test flags Unlike the compile-time TEST_FLAGS, this is read at run time so a single test can opt into flags such as -Zmir-preserve-ub through an ignore-if directive that checks whether the variable is set. --- tests/lang_tests.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/lang_tests.rs b/tests/lang_tests.rs index 6afd54e1c3f..e3baf1e038f 100644 --- a/tests/lang_tests.rs +++ b/tests/lang_tests.rs @@ -172,6 +172,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()]); From fdd3536ce6084ad1d106dbb20c11d04e0b17409d Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Fri, 29 May 2026 16:04:43 -0300 Subject: [PATCH 3/4] Add regression test for empty switch under mir-preserve-ub A range-pattern match compiled with -Zmir-preserve-ub leaves a SwitchInt with no cases whose discriminant is a bool comparison result. The test is skipped unless CARGO_TEST_FLAGS is set so it only runs when that flag is passed. --- tests/run/mir_preserve_ub_empty_switch.rs | 35 +++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/run/mir_preserve_ub_empty_switch.rs diff --git a/tests/run/mir_preserve_ub_empty_switch.rs b/tests/run/mir_preserve_ub_empty_switch.rs new file mode 100644 index 00000000000..26056360b92 --- /dev/null +++ b/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 +} From a2d570d57d2bd77fc036992b83cddd1011ff5318 Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Sat, 30 May 2026 15:01:26 -0300 Subject: [PATCH 4/4] Run the mir-preserve-ub switch test in CI The test opts into -Zmir-preserve-ub through CARGO_TEST_FLAGS and is skipped when that variable is unset, so the default cargo-tests run never exercises it. Invoke it explicitly with the flag set. --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa9535a3729..764ace91670 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -98,6 +98,7 @@ jobs: 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: |