From 2249fa70d690b054ec38459c5401b472728c5acd Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Wed, 9 Sep 2026 14:23:23 +0200 Subject: [PATCH 1/2] add test for parallel reproducible async fn and RPITIT --- .../parallel-reproducible-async-fn/rmake.rs | 62 +++++++++++++++++++ .../rpit-issue-162202.rs | 22 +++++++ 2 files changed, 84 insertions(+) create mode 100644 tests/run-make/parallel-reproducible-async-fn/rmake.rs create mode 100644 tests/run-make/parallel-reproducible-async-fn/rpit-issue-162202.rs diff --git a/tests/run-make/parallel-reproducible-async-fn/rmake.rs b/tests/run-make/parallel-reproducible-async-fn/rmake.rs new file mode 100644 index 0000000000000..ebd1e4c36b200 --- /dev/null +++ b/tests/run-make/parallel-reproducible-async-fn/rmake.rs @@ -0,0 +1,62 @@ +//@ needs-target-std +//@ ignore-cross-compile +//@ ignore-windows-gnu +// GNU Linker for Windows is non-deterministic. (from `reproducible-build-2` test in this suite) + +use std::rc::Rc; + +use run_make_support::{rfs, run_in_tmpdir, rustc}; + +/// Test that parallel compiler produces identical binaries. +fn main() { + const FILE_NAME: &str = "rpit-issue-162202"; + let rmeta_name = format!("{FILE_NAME}.rmeta"); + + let mut reference = None; + let mut reference_stderr = None; + + for _ in 0..10 { + // Tmp dir as previous runs affect output binary on windows. + run_in_tmpdir(|| { + let mut rustc = rustc(); + rustc + .input(format!("{FILE_NAME}.rs")) + .arg("--edition=2024") + .arg("-Zremap-cwd-prefix=reproducible_dir") + .arg("-Ccodegen-units=1") + .arg("-Zthreads=2") + .arg("--crate-type=lib") + .emit("metadata") + .output(&rmeta_name); + + let current_stderr = rustc.run().stderr_utf8(); + + let current = Rc::new(rfs::read(&rmeta_name)); + reference.get_or_insert(Rc::clone(¤t)); + let reference_stderr = reference_stderr.get_or_insert_with(|| current_stderr.clone()); + + if Some(current.clone()) != reference { + let reference_bytes = reference.as_ref().unwrap(); + let (pos, (left_byte, right_byte)) = current + .iter() + .zip(reference_bytes.iter()) + .enumerate() + .find(|(_, (c, r))| c != r) + .unwrap(); + let range_start = pos.saturating_sub(1); + let range_end = (pos + 3).min(current.len()).min(reference_bytes.len()); + panic!( + "left: {current:x?}\nright: {reference:x?}\n \ + differs at byte {pos}: left = {left_byte:#x}, right = {right_byte:#x}\n\ + left range [{range_start}..{range_end}]: {:x?}\n\ + right range [{range_start}..{range_end}]: {:x?}\n\ + left stderr:\n{current_stderr}\n\ + right stderr:\n{reference_stderr}", + ¤t[range_start..range_end], + &reference_bytes[range_start..range_end], + ) + } + assert_eq!(Some(current), reference); + }); + } +} diff --git a/tests/run-make/parallel-reproducible-async-fn/rpit-issue-162202.rs b/tests/run-make/parallel-reproducible-async-fn/rpit-issue-162202.rs new file mode 100644 index 0000000000000..fcc9d5ba26d41 --- /dev/null +++ b/tests/run-make/parallel-reproducible-async-fn/rpit-issue-162202.rs @@ -0,0 +1,22 @@ +trait Foo { + fn test() -> impl IntoIterator + Send; +} + +struct A; +impl Foo for A { + fn test() -> impl IntoIterator + Send { + [] + } +} + +struct B; +impl Foo for B { + fn test() -> impl IntoIterator + Send { + [] + } +} + +async fn test1(_: &'_ u8) {} +async fn test2<'s>(_: &'s u8) {} + +fn main() {} From cb6e6d3fa5d100b8ce9dc0d3df5d27a3421408d2 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Wed, 9 Sep 2026 14:27:18 +0200 Subject: [PATCH 2/2] make sure that DefId-s generated in queries during typecheck are done in deterministic order regardless of parallelism --- .../rustc_hir_analysis/src/check/wfcheck.rs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 34aafd72526a8..cfe5bda3092a4 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -2531,7 +2531,38 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { } } +/// `associated_types_for_impl_traits_in_trait_or_impl` creates new `DefId`-s inside the query. We +/// must make sure this is done in a deterministic order (not in parallel). +fn assign_anon_assoc_item_def_ids(tcx: TyCtxt<'_>) { + let items = tcx.hir_crate_items(()); + for def_id in items.free_items().map(|item| item.owner_id.def_id) { + match tcx.def_kind(def_id) { + DefKind::Trait | DefKind::Impl { .. } => { + tcx.ensure_ok().associated_types_for_impl_traits_in_trait_or_impl(def_id); + } + _ => (), + } + } +} + +/// `resolve_bound_vars` creates new `DefId`-s inside the query (in `remap_opaque_captures`). We +/// must make sure this is done in a deterministic order (not in parallel). +fn remap_opaque_captures(tcx: TyCtxt<'_>) { + let items = tcx.hir_crate_items(()); + for def_id in items.opaques() { + let opaque = tcx.hir_expect_opaque_ty(def_id); + let origin_id = match opaque.origin { + rustc_hir::OpaqueTyOrigin::TyAlias { parent, .. } + | rustc_hir::OpaqueTyOrigin::AsyncFn { parent, .. } + | rustc_hir::OpaqueTyOrigin::FnReturn { parent, .. } => parent, + }; + tcx.ensure_ok().resolve_bound_vars(rustc_hir::OwnerId { def_id: origin_id }); + } +} + pub(super) fn check_type_wf(tcx: TyCtxt<'_>, (): ()) -> Result<(), ErrorGuaranteed> { + assign_anon_assoc_item_def_ids(tcx); + remap_opaque_captures(tcx); let items = tcx.hir_crate_items(()); let res = items