about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-06-14 14:20:59 +0000
committerbors <bors@rust-lang.org>2023-06-14 14:20:59 +0000
commitafa9fef70904bee316d5a73275397d7c4e7c8c4b (patch)
treef4c520b318d66dbbcfd1c1aeb5962ff8154386c2 /src
parent7b0eac438ace0ba305b4633328b00474fbbf5120 (diff)
parentf67809ac1dca66e98c0999634212532974e43b97 (diff)
downloadrust-afa9fef70904bee316d5a73275397d7c4e7c8c4b.tar.gz
rust-afa9fef70904bee316d5a73275397d7c4e7c8c4b.zip
Auto merge of #112418 - ferrocene:pa-mir-opt-panic, r=ozkanonur,saethlin
Add support for targets without unwinding in `mir-opt`, and improve `--bless` for it

The main goal of this PR is to add support for targets without unwinding support in the `mir-opt` test suite, by adding the `EMIT_MIR_FOR_EACH_PANIC_STRATEGY` comment. Similarly to 32bit vs 64bit, when that comment is present, blessed output files will have the `.panic-unwind` or `.panic-abort` suffix, and the right one will be chosen depending on the target's panic strategy.

The `EMIT_MIR_FOR_EACH_PANIC_STRATEGY` comment replaced all the `ignore-wasm32` comments in the `mir-opt` test suite, as those comments were added due to `wasm32` being a target without unwinding support. The comment was also added on other tests that were only executed on x86 but were still panic strategy dependent.

The `mir-opt` suite was then blessed, which caused a ton of churn as most of the existing output files had to be renamed and (mostly) duplicated with the abort strategy.

---

After [asking on Zulip](https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/mir-opt.20tests.20and.20panic.3Dabort), the main concern about this change is it'd make blessing the `mir-opt` suite even harder, as you'd need to both bless it with an unwinding target and an aborting target. This exacerbated the current situation, where you'd need to bless it with a 32bit and a 64bit target already.

Because of that, this PR also makes significant enhancements to `--bless` for the `mir-opt` suite, where it will automatically bless the suite four times with different targets, while requiring minimal cross-compilation.

To handle the 32bit vs 64bit blessing, there is now an hardcoded list of target mapping between 32bit and 64bit. The goal of the list is to find a related target that will *probably* work without requiring additional cross-compilation toolchains on the system. If a mapping is found, bootstrap will bless the suite with both targets, otherwise just with the current target.

To handle the panic strategy blessing (abort vs unwind), I had to resort to what I call "synthetic targets". For each of the target we're blessing (so either the current one, or a 32bit and a 64bit depending on the previous paragraph), bootstrap will extract the JSON spec of the target and change it to include `"panic-strategy": "abort"`. It will then build the standard library with this synthetic target, and bless the `mir-opt` suite with it.

As a result of these changes, blessing the `mir-opt` suite will actually bless it two or four times with different targets, ensuring all possible variants are actually blessed.

---

This PR is best reviewed commit-by-commit.

r? `@jyn514`
cc `@saethlin` `@oli-obk`
Diffstat (limited to 'src')
-rw-r--r--src/bootstrap/builder.rs3
-rw-r--r--src/bootstrap/cc_detect.rs102
-rw-r--r--src/bootstrap/compile.rs19
-rw-r--r--src/bootstrap/config.rs16
-rw-r--r--src/bootstrap/dist.rs4
-rw-r--r--src/bootstrap/lib.rs67
-rw-r--r--src/bootstrap/llvm.rs12
-rw-r--r--src/bootstrap/synthetic_targets.rs82
-rw-r--r--src/bootstrap/test.rs108
-rw-r--r--src/bootstrap/tool.rs2
-rw-r--r--src/tools/compiletest/src/common.rs11
-rw-r--r--src/tools/compiletest/src/runtest.rs17
-rw-r--r--src/tools/miropt-test-tools/src/lib.rs53
-rw-r--r--src/tools/tidy/src/mir_opt_tests.rs7
14 files changed, 394 insertions, 109 deletions
diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs
index 51d94c48f7f..7c8e3536df5 100644
--- a/src/bootstrap/builder.rs
+++ b/src/bootstrap/builder.rs
@@ -1650,7 +1650,8 @@ impl<'a> Builder<'a> {
             }
         };
         cargo.env(profile_var("DEBUG"), debuginfo_level.to_string());
-        if self.cc[&target].args().iter().any(|arg| arg == "-gz") {
+        if !self.config.dry_run() && self.cc.borrow()[&target].args().iter().any(|arg| arg == "-gz")
+        {
             rustflags.arg("-Clink-arg=-gz");
         }
         cargo.env(
diff --git a/src/bootstrap/cc_detect.rs b/src/bootstrap/cc_detect.rs
index db3b69d18c8..ade3bfed11f 100644
--- a/src/bootstrap/cc_detect.rs
+++ b/src/bootstrap/cc_detect.rs
@@ -89,7 +89,7 @@ fn new_cc_build(build: &Build, target: TargetSelection) -> cc::Build {
     cfg
 }
 
-pub fn find(build: &mut Build) {
+pub fn find(build: &Build) {
     // For all targets we're going to need a C compiler for building some shims
     // and such as well as for being a linker for Rust code.
     let targets = build
@@ -100,60 +100,64 @@ pub fn find(build: &mut Build) {
         .chain(iter::once(build.build))
         .collect::<HashSet<_>>();
     for target in targets.into_iter() {
-        let mut cfg = new_cc_build(build, target);
-        let config = build.config.target_config.get(&target);
-        if let Some(cc) = config.and_then(|c| c.cc.as_ref()) {
-            cfg.compiler(cc);
-        } else {
-            set_compiler(&mut cfg, Language::C, target, config, build);
-        }
+        find_target(build, target);
+    }
+}
 
-        let compiler = cfg.get_compiler();
-        let ar = if let ar @ Some(..) = config.and_then(|c| c.ar.clone()) {
-            ar
-        } else {
-            cc2ar(compiler.path(), target)
-        };
+pub fn find_target(build: &Build, target: TargetSelection) {
+    let mut cfg = new_cc_build(build, target);
+    let config = build.config.target_config.get(&target);
+    if let Some(cc) = config.and_then(|c| c.cc.as_ref()) {
+        cfg.compiler(cc);
+    } else {
+        set_compiler(&mut cfg, Language::C, target, config, build);
+    }
 
-        build.cc.insert(target, compiler.clone());
-        let cflags = build.cflags(target, GitRepo::Rustc, CLang::C);
+    let compiler = cfg.get_compiler();
+    let ar = if let ar @ Some(..) = config.and_then(|c| c.ar.clone()) {
+        ar
+    } else {
+        cc2ar(compiler.path(), target)
+    };
 
-        // If we use llvm-libunwind, we will need a C++ compiler as well for all targets
-        // We'll need one anyways if the target triple is also a host triple
-        let mut cfg = new_cc_build(build, target);
-        cfg.cpp(true);
-        let cxx_configured = if let Some(cxx) = config.and_then(|c| c.cxx.as_ref()) {
-            cfg.compiler(cxx);
-            true
-        } else if build.hosts.contains(&target) || build.build == target {
-            set_compiler(&mut cfg, Language::CPlusPlus, target, config, build);
-            true
-        } else {
-            // Use an auto-detected compiler (or one configured via `CXX_target_triple` env vars).
-            cfg.try_get_compiler().is_ok()
-        };
+    build.cc.borrow_mut().insert(target, compiler.clone());
+    let cflags = build.cflags(target, GitRepo::Rustc, CLang::C);
 
-        // for VxWorks, record CXX compiler which will be used in lib.rs:linker()
-        if cxx_configured || target.contains("vxworks") {
-            let compiler = cfg.get_compiler();
-            build.cxx.insert(target, compiler);
-        }
+    // If we use llvm-libunwind, we will need a C++ compiler as well for all targets
+    // We'll need one anyways if the target triple is also a host triple
+    let mut cfg = new_cc_build(build, target);
+    cfg.cpp(true);
+    let cxx_configured = if let Some(cxx) = config.and_then(|c| c.cxx.as_ref()) {
+        cfg.compiler(cxx);
+        true
+    } else if build.hosts.contains(&target) || build.build == target {
+        set_compiler(&mut cfg, Language::CPlusPlus, target, config, build);
+        true
+    } else {
+        // Use an auto-detected compiler (or one configured via `CXX_target_triple` env vars).
+        cfg.try_get_compiler().is_ok()
+    };
 
-        build.verbose(&format!("CC_{} = {:?}", &target.triple, build.cc(target)));
-        build.verbose(&format!("CFLAGS_{} = {:?}", &target.triple, cflags));
-        if let Ok(cxx) = build.cxx(target) {
-            let cxxflags = build.cflags(target, GitRepo::Rustc, CLang::Cxx);
-            build.verbose(&format!("CXX_{} = {:?}", &target.triple, cxx));
-            build.verbose(&format!("CXXFLAGS_{} = {:?}", &target.triple, cxxflags));
-        }
-        if let Some(ar) = ar {
-            build.verbose(&format!("AR_{} = {:?}", &target.triple, ar));
-            build.ar.insert(target, ar);
-        }
+    // for VxWorks, record CXX compiler which will be used in lib.rs:linker()
+    if cxx_configured || target.contains("vxworks") {
+        let compiler = cfg.get_compiler();
+        build.cxx.borrow_mut().insert(target, compiler);
+    }
 
-        if let Some(ranlib) = config.and_then(|c| c.ranlib.clone()) {
-            build.ranlib.insert(target, ranlib);
-        }
+    build.verbose(&format!("CC_{} = {:?}", &target.triple, build.cc(target)));
+    build.verbose(&format!("CFLAGS_{} = {:?}", &target.triple, cflags));
+    if let Ok(cxx) = build.cxx(target) {
+        let cxxflags = build.cflags(target, GitRepo::Rustc, CLang::Cxx);
+        build.verbose(&format!("CXX_{} = {:?}", &target.triple, cxx));
+        build.verbose(&format!("CXXFLAGS_{} = {:?}", &target.triple, cxxflags));
+    }
+    if let Some(ar) = ar {
+        build.verbose(&format!("AR_{} = {:?}", &target.triple, ar));
+        build.ar.borrow_mut().insert(target, ar);
+    }
+
+    if let Some(ranlib) = config.and_then(|c| c.ranlib.clone()) {
+        build.ranlib.borrow_mut().insert(target, ranlib);
     }
 }
 
diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs
index c28fe9022ec..14c3ef79a78 100644
--- a/src/bootstrap/compile.rs
+++ b/src/bootstrap/compile.rs
@@ -169,6 +169,11 @@ impl Step for Std {
             cargo.arg("-p").arg(krate);
         }
 
+        // See src/bootstrap/synthetic_targets.rs
+        if target.is_synthetic() {
+            cargo.env("RUSTC_BOOTSTRAP_SYNTHETIC_TARGET", "1");
+        }
+
         let _guard = builder.msg(
             Kind::Build,
             compiler.stage,
@@ -314,7 +319,7 @@ fn copy_self_contained_objects(
         }
     } else if target.ends_with("windows-gnu") {
         for obj in ["crt2.o", "dllcrt2.o"].iter() {
-            let src = compiler_file(builder, builder.cc(target), target, CLang::C, obj);
+            let src = compiler_file(builder, &builder.cc(target), target, CLang::C, obj);
             let target = libdir_self_contained.join(obj);
             builder.copy(&src, &target);
             target_deps.push((target, DependencyType::TargetSelfContained));
@@ -995,8 +1000,13 @@ fn rustc_llvm_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelect
         && !target.contains("apple")
         && !target.contains("solaris")
     {
-        let file =
-            compiler_file(builder, builder.cxx(target).unwrap(), target, CLang::Cxx, "libstdc++.a");
+        let file = compiler_file(
+            builder,
+            &builder.cxx(target).unwrap(),
+            target,
+            CLang::Cxx,
+            "libstdc++.a",
+        );
         cargo.env("LLVM_STATIC_STDCPP", file);
     }
     if builder.llvm_link_shared() {
@@ -1267,6 +1277,9 @@ pub fn compiler_file(
     c: CLang,
     file: &str,
 ) -> PathBuf {
+    if builder.config.dry_run() {
+        return PathBuf::new();
+    }
     let mut cmd = Command::new(compiler);
     cmd.args(builder.cflags(target, GitRepo::Rustc, c));
     cmd.arg(format!("-print-file-name={}", file));
diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs
index b521ad75d63..8ee63e561ba 100644
--- a/src/bootstrap/config.rs
+++ b/src/bootstrap/config.rs
@@ -429,6 +429,7 @@ impl std::str::FromStr for RustcLto {
 pub struct TargetSelection {
     pub triple: Interned<String>,
     file: Option<Interned<String>>,
+    synthetic: bool,
 }
 
 /// Newtype over `Vec<TargetSelection>` so we can implement custom parsing logic
@@ -460,7 +461,15 @@ impl TargetSelection {
         let triple = INTERNER.intern_str(triple);
         let file = file.map(|f| INTERNER.intern_str(f));
 
-        Self { triple, file }
+        Self { triple, file, synthetic: false }
+    }
+
+    pub fn create_synthetic(triple: &str, file: &str) -> Self {
+        Self {
+            triple: INTERNER.intern_str(triple),
+            file: Some(INTERNER.intern_str(file)),
+            synthetic: true,
+        }
     }
 
     pub fn rustc_target_arg(&self) -> &str {
@@ -478,6 +487,11 @@ impl TargetSelection {
     pub fn ends_with(&self, needle: &str) -> bool {
         self.triple.ends_with(needle)
     }
+
+    // See src/bootstrap/synthetic_targets.rs
+    pub fn is_synthetic(&self) -> bool {
+        self.synthetic
+    }
 }
 
 impl fmt::Display for TargetSelection {
diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs
index 6a2409a0fbf..a34a594f137 100644
--- a/src/bootstrap/dist.rs
+++ b/src/bootstrap/dist.rs
@@ -170,6 +170,10 @@ fn make_win_dist(
     target: TargetSelection,
     builder: &Builder<'_>,
 ) {
+    if builder.config.dry_run() {
+        return;
+    }
+
     //Ask gcc where it keeps its stuff
     let mut cmd = Command::new(builder.cc(target));
     cmd.arg("-print-search-dirs");
diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs
index 7a16189926b..d7e77aeb338 100644
--- a/src/bootstrap/lib.rs
+++ b/src/bootstrap/lib.rs
@@ -61,6 +61,7 @@ mod run;
 mod sanity;
 mod setup;
 mod suggest;
+mod synthetic_targets;
 mod tarball;
 mod test;
 mod tool;
@@ -226,10 +227,10 @@ pub struct Build {
 
     // Runtime state filled in later on
     // C/C++ compilers and archiver for all targets
-    cc: HashMap<TargetSelection, cc::Tool>,
-    cxx: HashMap<TargetSelection, cc::Tool>,
-    ar: HashMap<TargetSelection, PathBuf>,
-    ranlib: HashMap<TargetSelection, PathBuf>,
+    cc: RefCell<HashMap<TargetSelection, cc::Tool>>,
+    cxx: RefCell<HashMap<TargetSelection, cc::Tool>>,
+    ar: RefCell<HashMap<TargetSelection, PathBuf>>,
+    ranlib: RefCell<HashMap<TargetSelection, PathBuf>>,
     // Miscellaneous
     // allow bidirectional lookups: both name -> path and path -> name
     crates: HashMap<Interned<String>, Crate>,
@@ -451,10 +452,10 @@ impl Build {
             miri_info,
             rustfmt_info,
             in_tree_llvm_info,
-            cc: HashMap::new(),
-            cxx: HashMap::new(),
-            ar: HashMap::new(),
-            ranlib: HashMap::new(),
+            cc: RefCell::new(HashMap::new()),
+            cxx: RefCell::new(HashMap::new()),
+            ar: RefCell::new(HashMap::new()),
+            ranlib: RefCell::new(HashMap::new()),
             crates: HashMap::new(),
             crate_paths: HashMap::new(),
             is_sudo,
@@ -482,7 +483,7 @@ impl Build {
         }
 
         build.verbose("finding compilers");
-        cc_detect::find(&mut build);
+        cc_detect::find(&build);
         // When running `setup`, the profile is about to change, so any requirements we have now may
         // be different on the next invocation. Don't check for them until the next time x.py is
         // run. This is ok because `setup` never runs any build commands, so it won't fail if commands are missing.
@@ -1103,16 +1104,22 @@ impl Build {
     }
 
     /// Returns the path to the C compiler for the target specified.
-    fn cc(&self, target: TargetSelection) -> &Path {
-        self.cc[&target].path()
+    fn cc(&self, target: TargetSelection) -> PathBuf {
+        if self.config.dry_run() {
+            return PathBuf::new();
+        }
+        self.cc.borrow()[&target].path().into()
     }
 
     /// Returns a list of flags to pass to the C compiler for the target
     /// specified.
     fn cflags(&self, target: TargetSelection, which: GitRepo, c: CLang) -> Vec<String> {
+        if self.config.dry_run() {
+            return Vec::new();
+        }
         let base = match c {
-            CLang::C => &self.cc[&target],
-            CLang::Cxx => &self.cxx[&target],
+            CLang::C => self.cc.borrow()[&target].clone(),
+            CLang::Cxx => self.cxx.borrow()[&target].clone(),
         };
 
         // Filter out -O and /O (the optimization flags) that we picked up from
@@ -1153,19 +1160,28 @@ impl Build {
     }
 
     /// Returns the path to the `ar` archive utility for the target specified.
-    fn ar(&self, target: TargetSelection) -> Option<&Path> {
-        self.ar.get(&target).map(|p| &**p)
+    fn ar(&self, target: TargetSelection) -> Option<PathBuf> {
+        if self.config.dry_run() {
+            return None;
+        }
+        self.ar.borrow().get(&target).cloned()
     }
 
     /// Returns the path to the `ranlib` utility for the target specified.
-    fn ranlib(&self, target: TargetSelection) -> Option<&Path> {
-        self.ranlib.get(&target).map(|p| &**p)
+    fn ranlib(&self, target: TargetSelection) -> Option<PathBuf> {
+        if self.config.dry_run() {
+            return None;
+        }
+        self.ranlib.borrow().get(&target).cloned()
     }
 
     /// Returns the path to the C++ compiler for the target specified.
-    fn cxx(&self, target: TargetSelection) -> Result<&Path, String> {
-        match self.cxx.get(&target) {
-            Some(p) => Ok(p.path()),
+    fn cxx(&self, target: TargetSelection) -> Result<PathBuf, String> {
+        if self.config.dry_run() {
+            return Ok(PathBuf::new());
+        }
+        match self.cxx.borrow().get(&target) {
+            Some(p) => Ok(p.path().into()),
             None => {
                 Err(format!("target `{}` is not configured as a host, only as a target", target))
             }
@@ -1173,21 +1189,24 @@ impl Build {
     }
 
     /// Returns the path to the linker for the given target if it needs to be overridden.
-    fn linker(&self, target: TargetSelection) -> Option<&Path> {
-        if let Some(linker) = self.config.target_config.get(&target).and_then(|c| c.linker.as_ref())
+    fn linker(&self, target: TargetSelection) -> Option<PathBuf> {
+        if self.config.dry_run() {
+            return Some(PathBuf::new());
+        }
+        if let Some(linker) = self.config.target_config.get(&target).and_then(|c| c.linker.clone())
         {
             Some(linker)
         } else if target.contains("vxworks") {
             // need to use CXX compiler as linker to resolve the exception functions
             // that are only existed in CXX libraries
-            Some(self.cxx[&target].path())
+            Some(self.cxx.borrow()[&target].path().into())
         } else if target != self.config.build
             && util::use_host_linker(target)
             && !target.contains("msvc")
         {
             Some(self.cc(target))
         } else if self.config.use_lld && !self.is_fuse_ld_lld(target) && self.build == target {
-            Some(&self.initial_lld)
+            Some(self.initial_lld.clone())
         } else {
             None
         }
diff --git a/src/bootstrap/llvm.rs b/src/bootstrap/llvm.rs
index 19e595650cf..4752b1f7ea1 100644
--- a/src/bootstrap/llvm.rs
+++ b/src/bootstrap/llvm.rs
@@ -605,7 +605,7 @@ fn configure_cmake(
     }
 
     let (cc, cxx) = match builder.config.llvm_clang_cl {
-        Some(ref cl) => (cl.as_ref(), cl.as_ref()),
+        Some(ref cl) => (cl.into(), cl.into()),
         None => (builder.cc(target), builder.cxx(target).unwrap()),
     };
 
@@ -656,9 +656,9 @@ fn configure_cmake(
                     .define("CMAKE_CXX_COMPILER_LAUNCHER", ccache);
             }
         }
-        cfg.define("CMAKE_C_COMPILER", sanitize_cc(cc))
-            .define("CMAKE_CXX_COMPILER", sanitize_cc(cxx))
-            .define("CMAKE_ASM_COMPILER", sanitize_cc(cc));
+        cfg.define("CMAKE_C_COMPILER", sanitize_cc(&cc))
+            .define("CMAKE_CXX_COMPILER", sanitize_cc(&cxx))
+            .define("CMAKE_ASM_COMPILER", sanitize_cc(&cc));
     }
 
     cfg.build_arg("-j").build_arg(builder.jobs().to_string());
@@ -698,7 +698,7 @@ fn configure_cmake(
         if ar.is_absolute() {
             // LLVM build breaks if `CMAKE_AR` is a relative path, for some reason it
             // tries to resolve this path in the LLVM build directory.
-            cfg.define("CMAKE_AR", sanitize_cc(ar));
+            cfg.define("CMAKE_AR", sanitize_cc(&ar));
         }
     }
 
@@ -706,7 +706,7 @@ fn configure_cmake(
         if ranlib.is_absolute() {
             // LLVM build breaks if `CMAKE_RANLIB` is a relative path, for some reason it
             // tries to resolve this path in the LLVM build directory.
-            cfg.define("CMAKE_RANLIB", sanitize_cc(ranlib));
+            cfg.define("CMAKE_RANLIB", sanitize_cc(&ranlib));
         }
     }
 
diff --git a/src/bootstrap/synthetic_targets.rs b/src/bootstrap/synthetic_targets.rs
new file mode 100644
index 00000000000..7eeac9025c9
--- /dev/null
+++ b/src/bootstrap/synthetic_targets.rs
@@ -0,0 +1,82 @@
+//! In some cases, parts of bootstrap need to change part of a target spec just for one or a few
+//! steps. Adding these targets to rustc proper would "leak" this implementation detail of
+//! bootstrap, and would make it more complex to apply additional changes if the need arises.
+//!
+//! To address that problem, this module implements support for "synthetic targets". Synthetic
+//! targets are custom target specs generated using builtin target specs as their base. You can use
+//! one of the target specs already defined in this module, or create new ones by adding a new step
+//! that calls create_synthetic_target.
+
+use crate::builder::{Builder, ShouldRun, Step};
+use crate::config::TargetSelection;
+use crate::Compiler;
+use std::process::{Command, Stdio};
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub(crate) struct MirOptPanicAbortSyntheticTarget {
+    pub(crate) compiler: Compiler,
+    pub(crate) base: TargetSelection,
+}
+
+impl Step for MirOptPanicAbortSyntheticTarget {
+    type Output = TargetSelection;
+    const DEFAULT: bool = true;
+    const ONLY_HOSTS: bool = false;
+
+    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
+        run.never()
+    }
+
+    fn run(self, builder: &Builder<'_>) -> Self::Output {
+        create_synthetic_target(builder, self.compiler, "miropt-abort", self.base, |spec| {
+            spec.insert("panic-strategy".into(), "abort".into());
+        })
+    }
+}
+
+fn create_synthetic_target(
+    builder: &Builder<'_>,
+    compiler: Compiler,
+    suffix: &str,
+    base: TargetSelection,
+    customize: impl FnOnce(&mut serde_json::Map<String, serde_json::Value>),
+) -> TargetSelection {
+    if base.contains("synthetic") {
+        // This check is not strictly needed, but nothing currently needs recursive synthetic
+        // targets. If the need arises, removing this in the future *SHOULD* be safe.
+        panic!("cannot create synthetic targets with other synthetic targets as their base");
+    }
+
+    let name = format!("{base}-synthetic-{suffix}");
+    let path = builder.out.join("synthetic-target-specs").join(format!("{name}.json"));
+    std::fs::create_dir_all(path.parent().unwrap()).unwrap();
+
+    if builder.config.dry_run() {
+        std::fs::write(&path, b"dry run\n").unwrap();
+        return TargetSelection::create_synthetic(&name, path.to_str().unwrap());
+    }
+
+    let mut cmd = Command::new(builder.rustc(compiler));
+    cmd.arg("--target").arg(base.rustc_target_arg());
+    cmd.args(["-Zunstable-options", "--print", "target-spec-json"]);
+    cmd.stdout(Stdio::piped());
+
+    let output = cmd.spawn().unwrap().wait_with_output().unwrap();
+    if !output.status.success() {
+        panic!("failed to gather the target spec for {base}");
+    }
+
+    let mut spec: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
+    let spec_map = spec.as_object_mut().unwrap();
+
+    // The `is-builtin` attribute of a spec needs to be removed, otherwise rustc will complain.
+    spec_map.remove("is-builtin");
+
+    customize(spec_map);
+
+    std::fs::write(&path, &serde_json::to_vec_pretty(&spec).unwrap()).unwrap();
+    let target = TargetSelection::create_synthetic(&name, path.to_str().unwrap());
+    crate::cc_detect::find_target(builder, target);
+
+    target
+}
diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs
index cde77f4720b..13a10b0d3a5 100644
--- a/src/bootstrap/test.rs
+++ b/src/bootstrap/test.rs
@@ -23,6 +23,7 @@ use crate::doc::DocumentationFormat;
 use crate::flags::Subcommand;
 use crate::llvm;
 use crate::render_tests::add_flags_and_try_run_tests;
+use crate::synthetic_targets::MirOptPanicAbortSyntheticTarget;
 use crate::tool::{self, SourceType, Tool};
 use crate::toolstate::ToolState;
 use crate::util::{self, add_link_lib_path, dylib_path, dylib_path_var, output, t, up_to_date};
@@ -30,6 +31,22 @@ use crate::{envify, CLang, DocTests, GitRepo, Mode};
 
 const ADB_TEST_DIR: &str = "/data/local/tmp/work";
 
+// mir-opt tests have different variants depending on whether a target is 32bit or 64bit, and
+// blessing them requires blessing with each target. To aid developers, when blessing the mir-opt
+// test suite the corresponding target of the opposite pointer size is also blessed.
+//
+// This array serves as the known mappings between 32bit and 64bit targets. If you're developing on
+// a target where a target with the opposite pointer size exists, feel free to add it here.
+const MIR_OPT_BLESS_TARGET_MAPPING: &[(&str, &str)] = &[
+    // (32bit, 64bit)
+    ("i686-unknown-linux-gnu", "x86_64-unknown-linux-gnu"),
+    ("i686-unknown-linux-musl", "x86_64-unknown-linux-musl"),
+    ("i686-pc-windows-msvc", "x86_64-pc-windows-msvc"),
+    ("i686-pc-windows-gnu", "x86_64-pc-windows-gnu"),
+    ("i686-apple-darwin", "x86_64-apple-darwin"),
+    ("i686-apple-darwin", "aarch64-apple-darwin"),
+];
+
 fn try_run(builder: &Builder<'_>, cmd: &mut Command) -> bool {
     if !builder.fail_fast {
         if !builder.try_run(cmd) {
@@ -1261,8 +1278,6 @@ default_test!(RunPassValgrind {
     suite: "run-pass-valgrind"
 });
 
-default_test!(MirOpt { path: "tests/mir-opt", mode: "mir-opt", suite: "mir-opt" });
-
 default_test!(Codegen { path: "tests/codegen", mode: "codegen", suite: "codegen" });
 
 default_test!(CodegenUnits {
@@ -1299,6 +1314,91 @@ host_test!(RunMakeFullDeps {
 
 default_test!(Assembly { path: "tests/assembly", mode: "assembly", suite: "assembly" });
 
+// For the mir-opt suite we do not use macros, as we need custom behavior when blessing.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub struct MirOpt {
+    pub compiler: Compiler,
+    pub target: TargetSelection,
+}
+
+impl Step for MirOpt {
+    type Output = ();
+    const DEFAULT: bool = true;
+    const ONLY_HOSTS: bool = false;
+
+    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
+        run.suite_path("tests/mir-opt")
+    }
+
+    fn make_run(run: RunConfig<'_>) {
+        let compiler = run.builder.compiler(run.builder.top_stage, run.build_triple());
+        run.builder.ensure(MirOpt { compiler, target: run.target });
+    }
+
+    fn run(self, builder: &Builder<'_>) {
+        let run = |target| {
+            builder.ensure(Compiletest {
+                compiler: self.compiler,
+                target: target,
+                mode: "mir-opt",
+                suite: "mir-opt",
+                path: "tests/mir-opt",
+                compare_mode: None,
+            })
+        };
+
+        // We use custom logic to bless the mir-opt suite: mir-opt tests have multiple variants
+        // (32bit vs 64bit, and panic=abort vs panic=unwind), and all of them needs to be blessed.
+        // When blessing, we try best-effort to also bless the other variants, to aid developers.
+        if builder.config.cmd.bless() {
+            let targets = MIR_OPT_BLESS_TARGET_MAPPING
+                .iter()
+                .filter(|(target_32bit, target_64bit)| {
+                    *target_32bit == &*self.target.triple || *target_64bit == &*self.target.triple
+                })
+                .next()
+                .map(|(target_32bit, target_64bit)| {
+                    let target_32bit = TargetSelection::from_user(target_32bit);
+                    let target_64bit = TargetSelection::from_user(target_64bit);
+
+                    // Running compiletest requires a C compiler to be available, but it might not
+                    // have been detected by bootstrap if the target we're testing wasn't in the
+                    // --target flags.
+                    if !builder.cc.borrow().contains_key(&target_32bit) {
+                        crate::cc_detect::find_target(builder, target_32bit);
+                    }
+                    if !builder.cc.borrow().contains_key(&target_64bit) {
+                        crate::cc_detect::find_target(builder, target_64bit);
+                    }
+
+                    vec![target_32bit, target_64bit]
+                })
+                .unwrap_or_else(|| {
+                    eprintln!(
+                        "\
+Note that not all variants of mir-opt tests are going to be blessed, as no mapping between
+a 32bit and a 64bit target was found for {target}.
+You can add that mapping by changing MIR_OPT_BLESS_TARGET_MAPPING in src/bootstrap/test.rs",
+                        target = self.target,
+                    );
+                    vec![self.target]
+                });
+
+            for target in targets {
+                run(target);
+
+                let panic_abort_target = builder.ensure(MirOptPanicAbortSyntheticTarget {
+                    compiler: self.compiler,
+                    base: target,
+                });
+                run(panic_abort_target);
+            }
+        } else {
+            run(self.target);
+        }
+    }
+}
+
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 struct Compiletest {
     compiler: Compiler,
@@ -1667,7 +1767,7 @@ note: if you're sure you want to do this, please open an issue as to why. In the
         // Note that if we encounter `PATH` we make sure to append to our own `PATH`
         // rather than stomp over it.
         if target.contains("msvc") {
-            for &(ref k, ref v) in builder.cc[&target].env() {
+            for &(ref k, ref v) in builder.cc.borrow()[&target].env() {
                 if k != "PATH" {
                     cmd.env(k, v);
                 }
@@ -1692,7 +1792,7 @@ note: if you're sure you want to do this, please open an issue as to why. In the
 
         cmd.arg("--adb-path").arg("adb");
         cmd.arg("--adb-test-dir").arg(ADB_TEST_DIR);
-        if target.contains("android") {
+        if target.contains("android") && !builder.config.dry_run() {
             // Assume that cc for this target comes from the android sysroot
             cmd.arg("--android-cross-path")
                 .arg(builder.cc(target).parent().unwrap().parent().unwrap());
diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs
index 962cbf758d4..96341b69df0 100644
--- a/src/bootstrap/tool.rs
+++ b/src/bootstrap/tool.rs
@@ -855,7 +855,7 @@ impl<'a> Builder<'a> {
         if compiler.host.contains("msvc") {
             let curpaths = env::var_os("PATH").unwrap_or_default();
             let curpaths = env::split_paths(&curpaths).collect::<Vec<_>>();
-            for &(ref k, ref v) in self.cc[&compiler.host].env() {
+            for &(ref k, ref v) in self.cc.borrow()[&compiler.host].env() {
                 if k != "PATH" {
                     continue;
                 }
diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs
index 96fe720630c..1b46c42fa4c 100644
--- a/src/tools/compiletest/src/common.rs
+++ b/src/tools/compiletest/src/common.rs
@@ -131,6 +131,15 @@ pub enum PanicStrategy {
     Abort,
 }
 
+impl PanicStrategy {
+    pub(crate) fn for_miropt_test_tools(&self) -> miropt_test_tools::PanicStrategy {
+        match self {
+            PanicStrategy::Unwind => miropt_test_tools::PanicStrategy::Unwind,
+            PanicStrategy::Abort => miropt_test_tools::PanicStrategy::Abort,
+        }
+    }
+}
+
 /// Configuration for compiletest
 #[derive(Debug, Default, Clone)]
 pub struct Config {
@@ -572,7 +581,7 @@ pub struct TargetCfg {
     #[serde(rename = "target-endian", default)]
     endian: Endian,
     #[serde(rename = "panic-strategy", default)]
-    panic: PanicStrategy,
+    pub(crate) panic: PanicStrategy,
 }
 
 impl TargetCfg {
diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs
index 6582b534488..7c6668b1c5d 100644
--- a/src/tools/compiletest/src/runtest.rs
+++ b/src/tools/compiletest/src/runtest.rs
@@ -3565,6 +3565,7 @@ impl<'test> TestCx<'test> {
         let files = miropt_test_tools::files_for_miropt_test(
             &self.testpaths.file,
             self.config.get_pointer_width(),
+            self.config.target_cfg().panic.for_miropt_test_tools(),
         );
 
         let mut out = Vec::new();
@@ -3582,25 +3583,24 @@ impl<'test> TestCx<'test> {
     }
 
     fn check_mir_dump(&self) {
-        let test_file_contents = fs::read_to_string(&self.testpaths.file).unwrap();
-
         let test_dir = self.testpaths.file.parent().unwrap();
         let test_crate =
             self.testpaths.file.file_stem().unwrap().to_str().unwrap().replace("-", "_");
 
-        let mut bit_width = String::new();
-        if test_file_contents.lines().any(|l| l == "// EMIT_MIR_FOR_EACH_BIT_WIDTH") {
-            bit_width = format!(".{}bit", self.config.get_pointer_width());
-        }
+        let suffix = miropt_test_tools::output_file_suffix(
+            &self.testpaths.file,
+            self.config.get_pointer_width(),
+            self.config.target_cfg().panic.for_miropt_test_tools(),
+        );
 
         if self.config.bless {
             for e in
-                glob(&format!("{}/{}.*{}.mir", test_dir.display(), test_crate, bit_width)).unwrap()
+                glob(&format!("{}/{}.*{}.mir", test_dir.display(), test_crate, suffix)).unwrap()
             {
                 std::fs::remove_file(e.unwrap()).unwrap();
             }
             for e in
-                glob(&format!("{}/{}.*{}.diff", test_dir.display(), test_crate, bit_width)).unwrap()
+                glob(&format!("{}/{}.*{}.diff", test_dir.display(), test_crate, suffix)).unwrap()
             {
                 std::fs::remove_file(e.unwrap()).unwrap();
             }
@@ -3609,6 +3609,7 @@ impl<'test> TestCx<'test> {
         let files = miropt_test_tools::files_for_miropt_test(
             &self.testpaths.file,
             self.config.get_pointer_width(),
+            self.config.target_cfg().panic.for_miropt_test_tools(),
         );
         for miropt_test_tools::MiroptTestFiles { from_file, to_file, expected_file, passes: _ } in
             files
diff --git a/src/tools/miropt-test-tools/src/lib.rs b/src/tools/miropt-test-tools/src/lib.rs
index f86c3ce0afe..e33ecfe8eab 100644
--- a/src/tools/miropt-test-tools/src/lib.rs
+++ b/src/tools/miropt-test-tools/src/lib.rs
@@ -1,4 +1,5 @@
 use std::fs;
+use std::path::Path;
 
 pub struct MiroptTestFiles {
     pub expected_file: std::path::PathBuf,
@@ -8,18 +9,52 @@ pub struct MiroptTestFiles {
     pub passes: Vec<String>,
 }
 
-pub fn files_for_miropt_test(testfile: &std::path::Path, bit_width: u32) -> Vec<MiroptTestFiles> {
+pub enum PanicStrategy {
+    Unwind,
+    Abort,
+}
+
+pub fn output_file_suffix(
+    testfile: &Path,
+    bit_width: u32,
+    panic_strategy: PanicStrategy,
+) -> String {
+    let mut each_bit_width = false;
+    let mut each_panic_strategy = false;
+    for line in fs::read_to_string(testfile).unwrap().lines() {
+        if line == "// EMIT_MIR_FOR_EACH_BIT_WIDTH" {
+            each_bit_width = true;
+        }
+        if line == "// EMIT_MIR_FOR_EACH_PANIC_STRATEGY" {
+            each_panic_strategy = true;
+        }
+    }
+
+    let mut suffix = String::new();
+    if each_bit_width {
+        suffix.push_str(&format!(".{}bit", bit_width));
+    }
+    if each_panic_strategy {
+        match panic_strategy {
+            PanicStrategy::Unwind => suffix.push_str(".panic-unwind"),
+            PanicStrategy::Abort => suffix.push_str(".panic-abort"),
+        }
+    }
+    suffix
+}
+
+pub fn files_for_miropt_test(
+    testfile: &std::path::Path,
+    bit_width: u32,
+    panic_strategy: PanicStrategy,
+) -> Vec<MiroptTestFiles> {
     let mut out = Vec::new();
     let test_file_contents = fs::read_to_string(&testfile).unwrap();
 
     let test_dir = testfile.parent().unwrap();
     let test_crate = testfile.file_stem().unwrap().to_str().unwrap().replace('-', "_");
 
-    let bit_width = if test_file_contents.lines().any(|l| l == "// EMIT_MIR_FOR_EACH_BIT_WIDTH") {
-        format!(".{}bit", bit_width)
-    } else {
-        String::new()
-    };
+    let suffix = output_file_suffix(testfile, bit_width, panic_strategy);
 
     for l in test_file_contents.lines() {
         if l.starts_with("// EMIT_MIR ") {
@@ -37,7 +72,7 @@ pub fn files_for_miropt_test(testfile: &std::path::Path, bit_width: u32) -> Vec<
                 passes.push(trimmed.split('.').last().unwrap().to_owned());
                 let test_against = format!("{}.after.mir", trimmed);
                 from_file = format!("{}.before.mir", trimmed);
-                expected_file = format!("{}{}.diff", trimmed, bit_width);
+                expected_file = format!("{}{}.diff", trimmed, suffix);
                 assert!(test_names.next().is_none(), "two mir pass names specified for MIR diff");
                 to_file = Some(test_against);
             } else if let Some(first_pass) = test_names.next() {
@@ -51,7 +86,7 @@ pub fn files_for_miropt_test(testfile: &std::path::Path, bit_width: u32) -> Vec<
                 assert!(test_names.next().is_none(), "three mir pass names specified for MIR diff");
 
                 expected_file =
-                    format!("{}{}.{}-{}.diff", test_name, bit_width, first_pass, second_pass);
+                    format!("{}{}.{}-{}.diff", test_name, suffix, first_pass, second_pass);
                 let second_file = format!("{}.{}.mir", test_name, second_pass);
                 from_file = format!("{}.{}.mir", test_name, first_pass);
                 to_file = Some(second_file);
@@ -64,7 +99,7 @@ pub fn files_for_miropt_test(testfile: &std::path::Path, bit_width: u32) -> Vec<
                 let extension = cap.get(1).unwrap().as_str();
 
                 expected_file =
-                    format!("{}{}{}", test_name.trim_end_matches(extension), bit_width, extension,);
+                    format!("{}{}{}", test_name.trim_end_matches(extension), suffix, extension,);
                 from_file = test_name.to_string();
                 assert!(test_names.next().is_none(), "two mir pass names specified for MIR dump");
                 to_file = None;
diff --git a/src/tools/tidy/src/mir_opt_tests.rs b/src/tools/tidy/src/mir_opt_tests.rs
index 2f6918510e8..c307bcb9390 100644
--- a/src/tools/tidy/src/mir_opt_tests.rs
+++ b/src/tools/tidy/src/mir_opt_tests.rs
@@ -1,5 +1,6 @@
 //! Tidy check to ensure that mir opt directories do not have stale files or dashes in file names
 
+use miropt_test_tools::PanicStrategy;
 use std::collections::HashSet;
 use std::path::{Path, PathBuf};
 
@@ -24,8 +25,10 @@ fn check_unused_files(path: &Path, bless: bool, bad: &mut bool) {
 
     for file in rs_files {
         for bw in [32, 64] {
-            for output_file in miropt_test_tools::files_for_miropt_test(&file, bw) {
-                output_files.remove(&output_file.expected_file);
+            for ps in [PanicStrategy::Unwind, PanicStrategy::Abort] {
+                for output_file in miropt_test_tools::files_for_miropt_test(&file, bw, ps) {
+                    output_files.remove(&output_file.expected_file);
+                }
             }
         }
     }