From 36e4fe5b5e73e24f0df6397151db16608d9f6de2 Mon Sep 17 00:00:00 2001 From: sjwang05 <63834813+sjwang05@users.noreply.github.com> Date: Fri, 12 Jun 2026 22:03:43 -0700 Subject: [PATCH 001/100] reject extern statics in promotion --- compiler/rustc_mir_transform/src/promote_consts.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/rustc_mir_transform/src/promote_consts.rs b/compiler/rustc_mir_transform/src/promote_consts.rs index 3694a0614a7b7..96057ad413802 100644 --- a/compiler/rustc_mir_transform/src/promote_consts.rs +++ b/compiler/rustc_mir_transform/src/promote_consts.rs @@ -317,6 +317,8 @@ impl<'tcx> Validator<'_, 'tcx> { // can only promote static accesses inside statics. && let Some(hir::ConstContext::Static(..)) = self.const_kind && !self.tcx.is_thread_local_static(did) + // Extern statics can never be read by CTFE, even inside a static. + && !self.tcx.is_foreign_item(did) { // Recurse. } else { From 9ac260686c2042fbff28381d69db5f6bc23fb3e6 Mon Sep 17 00:00:00 2001 From: sjwang05 <63834813+sjwang05@users.noreply.github.com> Date: Fri, 12 Jun 2026 22:03:56 -0700 Subject: [PATCH 002/100] re-bless tests --- ...0].SimplifyCfg-pre-optimizations.after.mir | 18 -------- ...motion_extern_static.FOO.PromoteTemps.diff | 44 ------------------- .../mir-opt/const_promotion_extern_static.rs | 7 --- .../extern-static-in-static-ice-143174.rs | 15 +++++++ .../extern-static-in-static-ice-143174.stderr | 18 ++++++++ .../extern-static-promotion-rejected.rs | 11 +++++ .../extern-static-promotion-rejected.stderr | 8 ++++ 7 files changed, 52 insertions(+), 69 deletions(-) delete mode 100644 tests/mir-opt/const_promotion_extern_static.FOO-promoted[0].SimplifyCfg-pre-optimizations.after.mir delete mode 100644 tests/mir-opt/const_promotion_extern_static.FOO.PromoteTemps.diff create mode 100644 tests/ui/statics/extern-static-in-static-ice-143174.rs create mode 100644 tests/ui/statics/extern-static-in-static-ice-143174.stderr create mode 100644 tests/ui/statics/extern-static-promotion-rejected.rs create mode 100644 tests/ui/statics/extern-static-promotion-rejected.stderr diff --git a/tests/mir-opt/const_promotion_extern_static.FOO-promoted[0].SimplifyCfg-pre-optimizations.after.mir b/tests/mir-opt/const_promotion_extern_static.FOO-promoted[0].SimplifyCfg-pre-optimizations.after.mir deleted file mode 100644 index 72cb64e275e36..0000000000000 --- a/tests/mir-opt/const_promotion_extern_static.FOO-promoted[0].SimplifyCfg-pre-optimizations.after.mir +++ /dev/null @@ -1,18 +0,0 @@ -// MIR for `FOO::promoted[0]` after SimplifyCfg-pre-optimizations - -const FOO::promoted[0]: &[&i32; 1] = { - let mut _0: &[&i32; 1]; - let mut _1: [&i32; 1]; - let mut _2: &i32; - let mut _3: *const i32; - - bb0: { - _3 = const {ALLOC0: *const i32}; - _2 = &(*_3); - _1 = [move _2]; - _0 = &_1; - return; - } -} - -ALLOC0 (extern static: X) diff --git a/tests/mir-opt/const_promotion_extern_static.FOO.PromoteTemps.diff b/tests/mir-opt/const_promotion_extern_static.FOO.PromoteTemps.diff deleted file mode 100644 index 0e4eed2c028d0..0000000000000 --- a/tests/mir-opt/const_promotion_extern_static.FOO.PromoteTemps.diff +++ /dev/null @@ -1,44 +0,0 @@ -- // MIR for `FOO` before PromoteTemps -+ // MIR for `FOO` after PromoteTemps - - static mut FOO: *const &i32 = { - let mut _0: *const &i32; - let mut _1: &[&i32]; - let mut _2: &[&i32; 1]; - let _3: [&i32; 1]; - let mut _4: &i32; - let _5: *const i32; -+ let mut _6: &[&i32; 1]; - - bb0: { - StorageLive(_1); - StorageLive(_2); -- StorageLive(_3); -- StorageLive(_4); -- StorageLive(_5); -- _5 = const {ALLOC0: *const i32}; -- _4 = &(*_5); -- _3 = [move _4]; -- _2 = &_3; -+ _6 = const FOO::promoted[0]; -+ _2 = &(*_6); - _1 = move _2 as &[&i32] (PointerCoercion(Unsize, Implicit)); -- StorageDead(_4); - StorageDead(_2); - _0 = core::slice::::as_ptr(move _1) -> [return: bb1, unwind: bb2]; - } - - bb1: { -- StorageDead(_5); -- StorageDead(_3); - StorageDead(_1); - return; - } - - bb2 (cleanup): { - resume; - } - } -- -- ALLOC0 (extern static: X) - diff --git a/tests/mir-opt/const_promotion_extern_static.rs b/tests/mir-opt/const_promotion_extern_static.rs index f16a53270a97d..ec9368094a752 100644 --- a/tests/mir-opt/const_promotion_extern_static.rs +++ b/tests/mir-opt/const_promotion_extern_static.rs @@ -1,18 +1,11 @@ //@ skip-filecheck //@ ignore-endian-big -extern "C" { - static X: i32; -} static Y: i32 = 42; // EMIT_MIR const_promotion_extern_static.BAR.PromoteTemps.diff // EMIT_MIR const_promotion_extern_static.BAR-promoted[0].SimplifyCfg-pre-optimizations.after.mir static mut BAR: *const &i32 = [&Y].as_ptr(); -// EMIT_MIR const_promotion_extern_static.FOO.PromoteTemps.diff -// EMIT_MIR const_promotion_extern_static.FOO-promoted[0].SimplifyCfg-pre-optimizations.after.mir -static mut FOO: *const &i32 = [unsafe { &X }].as_ptr(); - // EMIT_MIR const_promotion_extern_static.BOP.built.after.mir static BOP: &i32 = &13; diff --git a/tests/ui/statics/extern-static-in-static-ice-143174.rs b/tests/ui/statics/extern-static-in-static-ice-143174.rs new file mode 100644 index 0000000000000..868d9bab73663 --- /dev/null +++ b/tests/ui/statics/extern-static-in-static-ice-143174.rs @@ -0,0 +1,15 @@ +// Regression test for #143174. + +#![crate_type = "lib"] + +type Fun = unsafe extern "C" fn(); + +struct Foo(Fun); + +static FOO: &Foo = &Foo(BAR); +//~^ ERROR cannot access extern static `BAR` [E0080] +//~| ERROR use of extern static is unsafe and requires unsafe function or block [E0133] + +unsafe extern "C" { + static BAR: Fun; +} diff --git a/tests/ui/statics/extern-static-in-static-ice-143174.stderr b/tests/ui/statics/extern-static-in-static-ice-143174.stderr new file mode 100644 index 0000000000000..f38968031ba66 --- /dev/null +++ b/tests/ui/statics/extern-static-in-static-ice-143174.stderr @@ -0,0 +1,18 @@ +error[E0080]: cannot access extern static `BAR` + --> $DIR/extern-static-in-static-ice-143174.rs:9:25 + | +LL | static FOO: &Foo = &Foo(BAR); + | ^^^ evaluation of `FOO` failed here + +error[E0133]: use of extern static is unsafe and requires unsafe function or block + --> $DIR/extern-static-in-static-ice-143174.rs:9:25 + | +LL | static FOO: &Foo = &Foo(BAR); + | ^^^ use of extern static + | + = note: extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0080, E0133. +For more information about an error, try `rustc --explain E0080`. diff --git a/tests/ui/statics/extern-static-promotion-rejected.rs b/tests/ui/statics/extern-static-promotion-rejected.rs new file mode 100644 index 0000000000000..f024f00490f74 --- /dev/null +++ b/tests/ui/statics/extern-static-promotion-rejected.rs @@ -0,0 +1,11 @@ +// previously part of tests/mir-opt/const_promotion_extern_static.rs +// promotion of extern statics is now rejected entirely, even if we're not trying to read its value + +unsafe extern "C" { + static X: i32; +} + +static mut FOO: *const &i32 = [unsafe { &X }].as_ptr(); +//~^ ERROR dangling pointer + +fn main() {} diff --git a/tests/ui/statics/extern-static-promotion-rejected.stderr b/tests/ui/statics/extern-static-promotion-rejected.stderr new file mode 100644 index 0000000000000..52e24a0bd942c --- /dev/null +++ b/tests/ui/statics/extern-static-promotion-rejected.stderr @@ -0,0 +1,8 @@ +error: encountered dangling pointer in final value of mutable static + --> $DIR/extern-static-promotion-rejected.rs:8:1 + | +LL | static mut FOO: *const &i32 = [unsafe { &X }].as_ptr(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + From 0439f4ef09af50b440af245a0f2173442fbcd567 Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Thu, 23 Jul 2026 18:32:21 +0100 Subject: [PATCH 003/100] Hint that memchr returns an in-bounds index --- library/core/src/slice/memchr.rs | 7 ++++++- library/coretests/tests/slice.rs | 11 +++++++++++ .../codegen-llvm/lib-optimizations/memchr-result.rs | 13 +++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/codegen-llvm/lib-optimizations/memchr-result.rs diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index 1e1053583a617..6762015181d85 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -28,7 +28,12 @@ pub const fn memchr(x: u8, text: &[u8]) -> Option { return memchr_naive(x, text); } - memchr_aligned(x, text) + let result = memchr_aligned(x, text); + if let Some(index) = result { + // SAFETY: `memchr_aligned` only returns the index of a matching byte in `text`. + unsafe { crate::hint::assert_unchecked(index < text.len()) }; + } + result } #[inline] diff --git a/library/coretests/tests/slice.rs b/library/coretests/tests/slice.rs index a4db7304fff90..b05f54d4df0a2 100644 --- a/library/coretests/tests/slice.rs +++ b/library/coretests/tests/slice.rs @@ -1781,6 +1781,17 @@ pub mod memchr { assert_eq!(None, memchr(b'a', b"xyz")); } + #[test] + fn each_alignment() { + let mut data = [1u8; 64]; + let needle = 2; + let pos = 40; + data[pos] = needle; + for start in 0..16 { + assert_eq!(Some(pos - start), memchr(needle, &data[start..])); + } + } + #[test] fn matches_one_reversed() { assert_eq!(Some(0), memrchr(b'a', b"a")); diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs new file mode 100644 index 0000000000000..fbdbdcc3fe9f3 --- /dev/null +++ b/tests/codegen-llvm/lib-optimizations/memchr-result.rs @@ -0,0 +1,13 @@ +// Ensure `memchr` communicates that a returned index is in bounds. +//@ compile-flags: -Copt-level=3 -Zinline-mir=false +//@ only-64bit + +#![crate_type = "lib"] + +// CHECK-LABEL: @find_char +#[no_mangle] +pub fn find_char(haystack: &str, needle: char) -> Option { + // CHECK-NOT: phi { i64, i64 } + // CHECK: ret { i64, i64 } + haystack.find(needle) +} From c1f36d5f4bde0f955e9d0cbdec406d22b5044360 Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:29:23 +0100 Subject: [PATCH 004/100] Hint that memrchr returns an in-bounds index --- library/core/src/slice/memchr.rs | 10 ++++++++++ .../codegen-llvm/lib-optimizations/memchr-result.rs | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index 6762015181d85..c83e8b218da08 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -112,8 +112,18 @@ const fn memchr_aligned(x: u8, text: &[u8]) -> Option { } /// Returns the last index matching the byte `x` in `text`. +#[inline] #[must_use] pub fn memrchr(x: u8, text: &[u8]) -> Option { + let result = memrchr_aligned(x, text); + if let Some(index) = result { + // SAFETY: `memrchr_aligned` only returns the index of a matching byte in `text`. + unsafe { crate::hint::assert_unchecked(index < text.len()) }; + } + result +} + +fn memrchr_aligned(x: u8, text: &[u8]) -> Option { // Scan for a single byte value by reading two `usize` words at a time. // // Split `text` in three parts: diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs index fbdbdcc3fe9f3..f18335075451c 100644 --- a/tests/codegen-llvm/lib-optimizations/memchr-result.rs +++ b/tests/codegen-llvm/lib-optimizations/memchr-result.rs @@ -3,6 +3,11 @@ //@ only-64bit #![crate_type = "lib"] +#![feature(slice_internals)] + +extern crate core; + +use core::slice::memchr::memrchr; // CHECK-LABEL: @find_char #[no_mangle] @@ -11,3 +16,11 @@ pub fn find_char(haystack: &str, needle: char) -> Option { // CHECK: ret { i64, i64 } haystack.find(needle) } + +// CHECK-LABEL: @rfind_byte +#[no_mangle] +pub fn rfind_byte(haystack: &[u8], needle: u8) -> Option { + // CHECK-NOT: panic_bounds_check + // CHECK: ret { i1, i8 } + memrchr(needle, haystack).map(|index| haystack[index]) +} From 844c01e43be5782643646d73a6f65539db046a33 Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:14:14 +0000 Subject: [PATCH 005/100] Cover memchr fast path with bounds assertion --- library/core/src/slice/memchr.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index c83e8b218da08..017661f0448c2 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -24,13 +24,13 @@ const fn contains_zero_byte(x: usize) -> bool { #[must_use] pub const fn memchr(x: u8, text: &[u8]) -> Option { // Fast path for small slices. - if text.len() < 2 * USIZE_BYTES { - return memchr_naive(x, text); - } - - let result = memchr_aligned(x, text); + let result = if text.len() < 2 * USIZE_BYTES { + memchr_naive(x, text) + } else { + memchr_aligned(x, text) + }; if let Some(index) = result { - // SAFETY: `memchr_aligned` only returns the index of a matching byte in `text`. + // SAFETY: Both implementations only return an index from within `text`. unsafe { crate::hint::assert_unchecked(index < text.len()) }; } result From 49c1f02279a37b85fcd9448dc7b87e20923f57dd Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:32:44 +0000 Subject: [PATCH 006/100] Fix memchr result CI checks --- library/core/src/slice/memchr.rs | 7 ++----- tests/codegen-llvm/lib-optimizations/memchr-result.rs | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index 017661f0448c2..fb99e86139d7e 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -24,11 +24,8 @@ const fn contains_zero_byte(x: usize) -> bool { #[must_use] pub const fn memchr(x: u8, text: &[u8]) -> Option { // Fast path for small slices. - let result = if text.len() < 2 * USIZE_BYTES { - memchr_naive(x, text) - } else { - memchr_aligned(x, text) - }; + let result = + if text.len() < 2 * USIZE_BYTES { memchr_naive(x, text) } else { memchr_aligned(x, text) }; if let Some(index) = result { // SAFETY: Both implementations only return an index from within `text`. unsafe { crate::hint::assert_unchecked(index < text.len()) }; diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs index f18335075451c..77abc33adde83 100644 --- a/tests/codegen-llvm/lib-optimizations/memchr-result.rs +++ b/tests/codegen-llvm/lib-optimizations/memchr-result.rs @@ -1,6 +1,6 @@ // Ensure `memchr` communicates that a returned index is in bounds. //@ compile-flags: -Copt-level=3 -Zinline-mir=false -//@ only-64bit +//@ only-x86_64 #![crate_type = "lib"] #![feature(slice_internals)] From 19fe04b2d3e1b58815534e648202889b1eff56b0 Mon Sep 17 00:00:00 2001 From: zakrad <49591476+zakrad@users.noreply.github.com> Date: Tue, 28 Jul 2026 22:18:27 +0330 Subject: [PATCH 007/100] Add regression test for GAT bound mismatched-type error Proving `T::Assoc<_>: Sized` while a where-clause bound `T::Assoc: Sized` was in scope used to over-eagerly infer the unconstrained argument to `u8`, causing a spurious "mismatched types" error. It should compile; lock that in. --- .../gat-bound-mismatch-106832.rs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests/ui/generic-associated-types/gat-bound-mismatch-106832.rs diff --git a/tests/ui/generic-associated-types/gat-bound-mismatch-106832.rs b/tests/ui/generic-associated-types/gat-bound-mismatch-106832.rs new file mode 100644 index 0000000000000..943bc3b32b4b1 --- /dev/null +++ b/tests/ui/generic-associated-types/gat-bound-mismatch-106832.rs @@ -0,0 +1,30 @@ +//! Regression test for . +//! +//! Proving `T::Assoc<_>: Sized` while a where-clause bound `T::Assoc: Sized` is +//! in scope used to over-eagerly infer the otherwise-unconstrained argument to `u8`, +//! producing a spurious "mismatched types" error. This should compile. + +//@ check-pass + +#![allow(dead_code)] + +trait Trait { + type Assoc; +} + +fn test() +where + T::Assoc: Sized, +{ + // `_` must be inferred from the `1i32` argument, not eagerly unified with `u8` + // just because `T::Assoc: Sized` happens to be in the environment. + constrain::(1i32); +} + +fn constrain(_: A) +where + T::Assoc: Sized, +{ +} + +fn main() {} From 807750a1fcdb31f4bf527089ff44cf95ac199046 Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Tue, 28 Jul 2026 20:57:45 +0000 Subject: [PATCH 008/100] Preserve memchr codegen on LLVM 21 --- library/core/src/slice/memchr.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index fb99e86139d7e..68826ecac31f3 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -24,10 +24,18 @@ const fn contains_zero_byte(x: usize) -> bool { #[must_use] pub const fn memchr(x: u8, text: &[u8]) -> Option { // Fast path for small slices. - let result = - if text.len() < 2 * USIZE_BYTES { memchr_naive(x, text) } else { memchr_aligned(x, text) }; + if text.len() < 2 * USIZE_BYTES { + let result = memchr_naive(x, text); + if let Some(index) = result { + // SAFETY: `memchr_naive` only returns an index from within `text`. + unsafe { crate::hint::assert_unchecked(index < text.len()) }; + } + return result; + } + + let result = memchr_aligned(x, text); if let Some(index) = result { - // SAFETY: Both implementations only return an index from within `text`. + // SAFETY: `memchr_aligned` only returns an index from within `text`. unsafe { crate::hint::assert_unchecked(index < text.len()) }; } result From 1e1aad2b026e15776d7e764cc6da97969ba1ddc5 Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 21 Jul 2026 13:05:02 +1000 Subject: [PATCH 009/100] Inline the splatted_callee function --- compiler/rustc_mir_build/src/thir/cx/expr.rs | 136 +++++++++---------- 1 file changed, 61 insertions(+), 75 deletions(-) diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 4b067a8ca79e2..badce1d168138 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -1224,85 +1224,32 @@ impl<'tcx> ThirBuildCx<'tcx> { } } - fn splatted_callee( - &mut self, - expr: &hir::Expr<'_>, - span: Span, - ) -> (Expr<'tcx>, u16 /* arg_index */, u16 /* arg_count */) { - let SplattedDef { def_id, arg_index, arg_count } = - self.typeck_results.splatted_def(expr.hir_id).unwrap_or_else(|| { - span_bug!(expr.span, "no splatted def for function or method callee") - }); - - let expr = if let Some(def_id) = def_id { - // We're calling a function via a FnDef, and its possibly generic type - let def_kind = self.tcx.def_kind(def_id); - let user_ty = self.user_args_applied_to_res(expr.hir_id, Res::Def(def_kind, def_id)); - debug!( - "splatted_callee FnDef: user_ty={:?} def_kind={:?} def_id={:?} arg_index={:?} arg_count={:?}", - user_ty, def_kind, def_id, arg_index, arg_count, - ); - - Expr { - temp_scope_id: expr.hir_id.local_id, - ty: self - .tcx - .type_of(def_id) - .instantiate(self.tcx, self.typeck_results.node_args(expr.hir_id)) - .skip_norm_wip(), - span, - kind: ExprKind::ZstLiteral { user_ty }, - } - } else { - // We're calling a function via a FnPtr and its type - // FIXME(splat): populate the side-tables for FnPtrs, using liberated_fn_sigs if needed - let fn_ty = self.typeck_results.expr_ty_adjusted(expr); - let user_ty = - self.typeck_results.user_provided_types().get(expr.hir_id).copied().map(Box::new); - debug!( - "splatted_callee FnPtr: user_ty={:?} fn_ty={:?} arg_index={:?} arg_count={:?}", - user_ty, fn_ty, arg_index, arg_count, - ); - - if !fn_ty.is_fn() { - span_bug!(expr.span, "splatted FnPtr side-tables are not yet implemented") - } - - Expr { - temp_scope_id: expr.hir_id.local_id, - // Create a new FnPtr FnSig type, representing the splatted function arguments with - // user-supplied generic types applied - ty: Ty::new_fn_ptr(self.tcx, fn_ty.fn_sig(self.tcx)), - span, - kind: ExprKind::ZstLiteral { user_ty }, - } - }; - - (expr, arg_index, arg_count) - } - /// The callee has a splatted tuple argument. /// Rewrite a splatted call `receiver.f(a, u, v)` into `receiver.f(a, #[rustc_splat] (u, v))`. /// The receiver is optional. fn convert_splatted_callee( &mut self, - expr: &hir::Expr<'_>, + call_expr: &'tcx hir::Expr<'_>, fn_span: Span, args: &'tcx [hir::Expr<'tcx>], receiver: Option<&'tcx hir::Expr<'tcx>>, ) -> ExprKind<'tcx> { let tcx = self.tcx; - // The callee has a splatted tuple argument. - let (func, tupled_arg_index, tupled_args_count) = self.splatted_callee(expr, fn_span); - let tupled_arg_index = usize::from(tupled_arg_index); - let tupled_args_count = usize::from(tupled_args_count); + // Look up the typeck results + let splatted_def = + self.typeck_results.splatted_def(call_expr.hir_id).unwrap_or_else(|| { + span_bug!(call_expr.span, "no splatted def for function or method callee") + }); + + let tupled_arg_index = usize::from(splatted_def.arg_index); + let tupled_args_count = usize::from(splatted_def.arg_count); // Splatting an empty tuple is permitted: `a.f() -> Trait::f(a, #[rustc_splat] ())`. // In that case, the tupled arg index is one past the end of the args. if tupled_arg_index + tupled_args_count > args.len() { span_bug!( - expr.span, + call_expr.span, "splatted arg index out of bounds of function args: {:?} + {:?} > {:?} for function call: receiver {:?}, args {:?}", tupled_arg_index, tupled_args_count, @@ -1312,7 +1259,7 @@ impl<'tcx> ThirBuildCx<'tcx> { ); } - info!("Using splatted function span: {:?}", func.span); + debug!("Using splatted function span: {:?}", fn_span); // Split into non-tupled and tupled arguments let initial_non_tupled_args = @@ -1331,29 +1278,68 @@ impl<'tcx> ThirBuildCx<'tcx> { let tupled_arg_tys = tupled_args.iter().map(|e| self.typeck_results.expr_ty_adjusted(e)); - let temp_scope_id = - if receiver.is_some() { func.temp_scope_id } else { expr.hir_id.local_id }; + // We need the tupled arguments in HIR/MIR for type checking + // FIXME(splat): de-tuple args in codegen for performance let tupled_args = Expr { ty: Ty::new_tup_from_iter(tcx, tupled_arg_tys), - temp_scope_id, - span: expr.span, + temp_scope_id: call_expr.hir_id.local_id, + span: call_expr.span, kind: ExprKind::Tuple { fields: self.mirror_exprs(tupled_args) }, }; let tupled_args = self.thir.exprs.push(tupled_args); - let mut args = - if let Some(receiver) = receiver { vec![self.mirror_expr(receiver)] } else { vec![] }; + // Handle the receiver as the first arg, if present + let mut args = Vec::with_capacity( + usize::from(receiver.is_some()) + + initial_non_tupled_args.len() + + 1 + + final_non_tupled_args.len(), + ); + if let Some(receiver) = receiver { + args.push(self.mirror_expr(receiver)); + } args.extend(initial_non_tupled_args); args.push(tupled_args); args.extend(final_non_tupled_args); - // We need the tupled arguments in HIR/MIR for type checking, but codegen can - // de-tuple them for performance - let fn_span = if receiver.is_some() { func.span } else { expr.span }; + let fn_span = if receiver.is_some() { fn_span } else { call_expr.span }; + + let (fn_ty, fun_expr) = match (splatted_def, receiver) { + // Create a FnDef shim for user-provided types + (SplattedDef { def_id: Some(def_id), arg_index, arg_count }, _) => { + // We're calling a function via a FnDef, and its possibly generic type + // This is effectively `self.method_callee(call_expr, fn_span, None)`, + // applied to `splatted_def` instead of `type_dependent_def`. + let def_kind = self.tcx.def_kind(def_id); + let user_ty = + self.user_args_applied_to_res(call_expr.hir_id, Res::Def(def_kind, def_id)); + debug!( + "splatted_callee FnDef: user_ty={:?} def_kind={:?} def_id={:?} arg_index={:?} arg_count={:?}", + user_ty, def_kind, def_id, arg_index, arg_count, + ); + + // Create a new FnDef expression with user-provided type applied + let callee_expr = Expr { + temp_scope_id: call_expr.hir_id.local_id, + ty: self + .tcx + .type_of(def_id) + .instantiate(self.tcx, self.typeck_results.node_args(call_expr.hir_id)) + .skip_norm_wip(), + span: fn_span, + kind: ExprKind::ZstLiteral { user_ty }, + }; + (callee_expr.ty, self.thir.exprs.push(callee_expr)) + } + (SplattedDef { def_id: None, .. }, _) => { + span_bug!(call_expr.span, "splatted FnPtr side-tables are not yet implemented"); + } + }; + ExprKind::Call { - ty: func.ty, - fun: self.thir.exprs.push(func), + ty: fn_ty, + fun: fun_expr, args: args.into_boxed_slice(), from_hir_call: true, fn_span, From 9f9fb37f39e7813bb8a90bbaf5cfbc486fbab5cf Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 28 Jul 2026 14:24:06 +1000 Subject: [PATCH 010/100] Refactor splat using custom enums (with stubs) --- compiler/rustc_hir_typeck/src/callee.rs | 26 +++++-- compiler/rustc_hir_typeck/src/expr.rs | 11 +-- .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 42 ++++++++---- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 68 +++++++++++-------- .../rustc_middle/src/ty/typeck_results.rs | 68 +++++++++++++++---- compiler/rustc_mir_build/src/thir/cx/expr.rs | 66 ++++++++++++++---- 6 files changed, 204 insertions(+), 77 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 288a1903bf675..0caac74f2e4e1 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -31,6 +31,16 @@ use crate::method::TreatNotYetDefinedOpaques; use crate::method::confirm::ConfirmContext; use crate::method::probe::{IsSuggestion, Mode}; +/// Side-table info for lowering splatted function arguments. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub(crate) enum SplatLoweringInfo<'tcx> { + /// The DefId of the FnDef being called, used to look up the function type. + /// Also used during argument suggestion for non-splatted function calls. + FnDef(DefId), + /// FIXME(splat): Stub for non-FnDef + NotAFnDef(std::marker::PhantomData<&'tcx ()>), +} + /// Checks that it is legal to call methods of the trait corresponding /// to `trait_id` (this only cares about the trait, not the specific /// method that is called). @@ -600,13 +610,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); let fn_sig = self.normalize(call_expr.span, Unnormalized::new_wip(fn_sig)); + // Splatted FnDefs use the DefId to look up the type, FnPtrs need it directly + let fn_id = match def_id { + Some(x) => SplatLoweringInfo::FnDef(x), + None => SplatLoweringInfo::NotAFnDef(std::marker::PhantomData), + }; + self.check_argument_types_maybe_method_like( &fn_sig, call_expr, arg_exprs, expected, TupleArgumentsFlag::with_fn_sig_kind(fn_sig.fn_sig_kind, false), - def_id, + fn_id, callee_generic_args, ); @@ -643,7 +659,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs: &'tcx [hir::Expr<'tcx>], expected: Expectation<'tcx>, tuple_arguments_flag: TupleArgumentsFlag, - def_id: Option, + fn_id: SplatLoweringInfo<'tcx>, callee_generic_args: Option>, ) { let do_check = || { @@ -656,7 +672,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs, fn_sig.c_variadic(), tuple_arguments_flag, - def_id, + fn_id, callee_generic_args, ); }; @@ -1074,7 +1090,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs, fn_sig.fn_sig_kind.c_variadic(), TupleArgumentsFlag::rust_fn_trait_call(), - Some(closure_def_id.to_def_id()), + SplatLoweringInfo::FnDef(closure_def_id.to_def_id()), None, ); @@ -1172,7 +1188,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { arg_exprs, method.sig.fn_sig_kind.c_variadic(), TupleArgumentsFlag::rust_fn_trait_call(), - Some(method.def_id), + SplatLoweringInfo::FnDef(method.def_id), None, ); diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 6f9b6a4f14ce9..0e2720f4aa1d1 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -39,6 +39,7 @@ use rustc_trait_selection::traits::{self, ObligationCauseCode, ObligationCtxt}; use tracing::{debug, instrument, trace}; use crate::Expectation::{self, ExpectCastableToType, ExpectHasType, NoExpectation}; +use crate::callee::SplatLoweringInfo; use crate::coercion::CoerceMany; use crate::diagnostics::{ AddressOfTemporaryTaken, BaseExpressionDoubleDot, BaseExpressionDoubleDotAddExpr, @@ -1487,7 +1488,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { args, method.sig.fn_sig_kind.c_variadic(), method_tuple_args_flag, - Some(method.def_id), + SplatLoweringInfo::FnDef(method.def_id), Some(method.args), ); @@ -1499,22 +1500,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let guar = self.report_method_error(expr.hir_id, rcvr_t, error, expected, false); let err_inputs = self.err_args(args.len(), guar); - let err_output = Ty::new_error(self.tcx, guar); + let err_ty = Ty::new_error(self.tcx, guar); self.check_argument_types( segment.ident.span, expr, &err_inputs, - err_output, + err_ty, NoExpectation, args, false, TupleArgumentsFlag::DontTupleArguments, - None, + SplatLoweringInfo::NotAFnDef(std::marker::PhantomData), Some(GenericArgsRef::default()), ); - err_output + err_ty } } } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index 1886888c476a0..edfe1ec00f5e7 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -41,7 +41,7 @@ use rustc_trait_selection::traits::{ }; use tracing::{debug, instrument}; -use crate::callee::{self, DeferredCallResolution}; +use crate::callee::{self, DeferredCallResolution, SplatLoweringInfo}; use crate::diagnostics::{self, CtorIsPrivate}; use crate::method::{self, MethodCallee}; use crate::{BreakableCtxt, Diverges, Expectation, FnCtxt, LoweredTy}; @@ -238,7 +238,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub(crate) fn write_splatted_resolution( &self, hir_id: HirId, - r: Result, + r: Result, ErrorGuaranteed>, ) { self.typeck_results.borrow_mut().splatted_defs_mut().insert(hir_id, r); } @@ -260,7 +260,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, hir_id: HirId, span: Span, - callee_def_id: Option, + fn_id: SplatLoweringInfo<'tcx>, callee_generic_args: Option>, first_tupled_arg_index: u16, tupled_args_count: u16, @@ -268,16 +268,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // FIXME(const_trait_impl): enforce constness using enforce_context_effects() and add // _and_enforce_effects to this method's name - self.write_splatted_resolution( - hir_id, - Ok(SplattedDef { - def_id: callee_def_id, - arg_index: first_tupled_arg_index, - arg_count: tupled_args_count, - }), - ); - if let Some(callee_generic_args) = callee_generic_args { - self.write_args(hir_id, callee_generic_args); + match fn_id { + // We're splatting a FnDef based on its DefId + SplatLoweringInfo::FnDef(def_id) => { + self.write_splatted_resolution( + hir_id, + Ok(SplattedDef::FnDef { + def_id, + arg_index: first_tupled_arg_index, + arg_count: tupled_args_count, + }), + ); + if let Some(callee_generic_args) = callee_generic_args { + self.write_args(hir_id, callee_generic_args); + } + } + // FIXME(splat): handle FnPtrs + SplatLoweringInfo::NotAFnDef(_) => { + self.write_splatted_resolution( + hir_id, + Ok(SplattedDef::NotAFnDef { + not_yet_implemented: std::marker::PhantomData, + arg_index: first_tupled_arg_index, + arg_count: tupled_args_count, + }), + ); + } } } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 949aa32a0b605..cc614542015a2 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -32,6 +32,7 @@ use tracing::debug; use crate::Expectation::*; use crate::TupleArgumentsFlag::*; +use crate::callee::SplatLoweringInfo; use crate::coercion::CoerceMany; use crate::diagnostics::SuggestPtrNullMut; use crate::fn_ctxt::arg_matrix::{ArgMatrix, Compatibility, Error, ExpectedIdx, ProvidedIdx}; @@ -203,8 +204,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { c_variadic: bool, // Whether all the arguments have been bundled in a tuple (ex: closures), or one has been splatted tuple_arguments: TupleArgumentsFlag, - // The DefId for the function being called, for better error messages - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, // The generics of the function being called. Only used for splatting callee_generic_args: Option>, ) { @@ -301,7 +302,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_args, expected_input_tys, tuple_arguments, - fn_def_id, + fn_id, callee_generic_args, ); let TupledArgCheckOutcome { @@ -552,7 +553,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -575,8 +576,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { mut expected_input_tys: Option>>, // Whether all the arguments have been bundled in a tuple (ex: closures), or one has been splatted tuple_arguments: TupleArgumentsFlag, - // The DefId for the function being called, for better error messages - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, // The generics of the function being called. Only used for splatting callee_generic_args: Option>, ) -> TupledArgCheckOutcome<'tcx> { @@ -736,7 +737,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // If we don't check argument counts here, and there's a subtle bug in the code above, // later compilation stages can fail in unrelated places with confusing errors. if !matches!(tuple_type.kind(), ty::Tuple(_)) { - let spans = if let Some(def_id) = fn_def_id + let spans = if let SplatLoweringInfo::FnDef(def_id) = fn_id && let Some(hir_node) = self.tcx.hir_get_if_local(def_id) && let Some(fn_decl) = hir_node.fn_decl() && let Some(arg_ty) = fn_decl.inputs.get(first_tupled_arg_index_usz) @@ -797,7 +798,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.write_splatted_call( call_expr.hir_id, call_span, - fn_def_id, + fn_id, callee_generic_args, first_tupled_arg_index, tupled_args_count.unwrap().try_into().unwrap(), @@ -834,7 +835,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx hir::Expr<'tcx>, // FIXME(splat): when the feature design is settled, improve the errors here @@ -849,7 +851,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -923,7 +925,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Call out where the function is defined fn_call_diag_ctxt.label_fn_like( &mut err, - fn_def_id, + fn_id, fn_call_diag_ctxt.callee_ty, call_expr, None, @@ -1593,7 +1595,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn label_fn_like( &self, err: &mut Diag<'_>, - callable_def_id: Option, + // Lowering info if a splatted function is being called. + callable_id: SplatLoweringInfo<'tcx>, callee_ty: Option>, call_expr: &'tcx hir::Expr<'tcx>, expected_ty: Option>, @@ -1604,7 +1607,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { is_method: bool, tuple_arguments: TupleArgumentsFlag, ) { - let Some(mut def_id) = callable_def_id else { + let SplatLoweringInfo::FnDef(mut def_id) = callable_id else { + // FIXME(FnPtr, splat): Handle FnPtr types and splatting here return; }; @@ -1943,14 +1947,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn label_generic_mismatches( &self, err: &mut Diag<'_>, - callable_def_id: Option, + // Lowering info if a splatted function is being called. + callable_id: SplatLoweringInfo<'tcx>, matched_inputs: &IndexVec>, provided_arg_tys: &IndexVec, Span)>, formal_and_expected_inputs: &IndexVec, Ty<'tcx>)>, is_method: bool, is_splat: bool, ) { - let Some(def_id) = callable_def_id else { + let SplatLoweringInfo::FnDef(def_id) = callable_id else { + // FIXME(FnPtr, splat): Handle FnPtr types and splatting here return; }; @@ -2187,7 +2193,8 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -2199,7 +2206,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -2310,7 +2317,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { }; self.arg_matching_ctxt.args_ctxt.call_ctxt.fn_ctxt.label_fn_like( &mut err, - self.fn_def_id, + self.fn_id, self.callee_ty, self.call_expr, None, @@ -2468,7 +2475,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { // Call out where the function is defined self.label_fn_like( &mut err, - self.fn_def_id, + self.fn_id, self.callee_ty, self.call_expr, Some(expected_ty), @@ -2887,7 +2894,7 @@ impl<'a, 'tcx> FnCallDiagCtxt<'a, 'tcx> { fn label_generic_mismatches(&self, err: &mut Diag<'a>) { self.fn_ctxt.label_generic_mismatches( err, - self.fn_def_id, + self.fn_id, &self.matched_inputs, &self.provided_arg_tys, &self.formal_and_expected_inputs, @@ -3082,7 +3089,8 @@ impl<'a, 'tcx> ArgMatchingCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -3094,7 +3102,7 @@ impl<'a, 'tcx> ArgMatchingCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -3229,7 +3237,8 @@ impl<'a, 'tcx> ArgsCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -3241,7 +3250,7 @@ impl<'a, 'tcx> ArgsCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -3348,7 +3357,8 @@ struct CallCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + /// Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx hir::Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -3372,7 +3382,8 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { provided_args: IndexVec>, c_variadic: bool, err_code: ErrCode, - fn_def_id: Option, + // Lowering info if a splatted function is being called. + fn_id: SplatLoweringInfo<'tcx>, call_span: Span, call_expr: &'tcx hir::Expr<'tcx>, tuple_arguments: TupleArgumentsFlag, @@ -3404,7 +3415,7 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { provided_args, c_variadic, err_code, - fn_def_id, + fn_id, call_span, call_expr, tuple_arguments, @@ -3491,7 +3502,7 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { "()".to_string() } else if ty.is_suggestable(self.tcx, false) { with_forced_trimmed_paths!(format!("/* {ty} */")) - } else if let Some(fn_def_id) = self.fn_def_id + } else if let SplatLoweringInfo::FnDef(fn_def_id) = self.fn_id && self.tcx.def_kind(fn_def_id).is_fn_like() && let self_implicit = matches!(self.call_expr.kind, hir::ExprKind::MethodCall(..)) as usize @@ -3501,6 +3512,7 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { { format!("/* {} */", arg.name) } else { + // FIXME(FnPtr, splat): What suggestions are needed for FnPtrs? "/* value */".to_string() } } diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs index a0f38dcb50cb4..cf447eaa5838c 100644 --- a/compiler/rustc_middle/src/ty/typeck_results.rs +++ b/compiler/rustc_middle/src/ty/typeck_results.rs @@ -37,7 +37,7 @@ pub struct TypeckResults<'tcx> { type_dependent_defs: ItemLocalMap>, /// Resolved definitions for splatted function calls. - splatted_defs: ItemLocalMap>, + splatted_defs: ItemLocalMap, ErrorGuaranteed>>, /// Resolved field indices for field accesses in expressions (`S { field }`, `obj.field`) /// or patterns (`S { field }`). The index is often useful by itself, but to learn more @@ -295,18 +295,20 @@ impl<'tcx> TypeckResults<'tcx> { LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.type_dependent_defs } } - pub fn splatted_defs(&self) -> LocalTableInContext<'_, Result> { + pub fn splatted_defs( + &self, + ) -> LocalTableInContext<'_, Result, ErrorGuaranteed>> { LocalTableInContext { hir_owner: self.hir_owner, data: &self.splatted_defs } } - pub fn splatted_def(&self, id: HirId) -> Option { + pub fn splatted_def(&self, id: HirId) -> Option> { validate_hir_id_for_typeck_results(self.hir_owner, id); self.splatted_defs.get(&id.local_id).cloned().and_then(|r| r.ok()) } pub fn splatted_defs_mut( &mut self, - ) -> LocalTableInContextMut<'_, Result> { + ) -> LocalTableInContextMut<'_, Result, ErrorGuaranteed>> { LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.splatted_defs } } @@ -431,7 +433,7 @@ impl<'tcx> TypeckResults<'tcx> { } pub fn is_splatted_call(&self, expr: &hir::Expr<'_>) -> bool { - matches!(self.splatted_defs().get(expr.hir_id), Some(Ok(SplattedDef { .. }))) + matches!(self.splatted_defs().get(expr.hir_id), Some(Ok(_))) } /// Returns the computed binding mode for a `PatKind::Binding` pattern @@ -598,14 +600,54 @@ impl<'tcx> TypeckResults<'tcx> { /// A resolved splatted function call. #[derive(Debug, Copy, Clone, PartialEq, Eq, StableHash, TyEncodable, TyDecodable)] -pub struct SplattedDef { - /// The function DefId, if available (FnPtrs don't have DefIds) - pub def_id: Option, - /// The index of the first argument in the callee's splatted tuple, and the index of the - /// splatted tuple argument in the caller. - pub arg_index: u16, - /// The number of arguments in the splatted tuple. - pub arg_count: u16, +pub enum SplattedDef<'tcx> { + /// A resolved FnDef call. + FnDef { + /// The DefId of the FnDef (used to look up its type). + def_id: DefId, + + /// The index of the first argument in the callee's splatted tuple, and the index of the + /// splatted tuple argument in the caller. + arg_index: u16, + + /// The number of arguments in the splatted tuple. + arg_count: u16, + }, + + /// FIXME(splat): handle FnPtrs + NotAFnDef { + not_yet_implemented: std::marker::PhantomData<&'tcx ()>, + + /// The index of the first argument in the callee's splatted tuple, and the index of the + /// splatted tuple argument in the caller. + arg_index: u16, + + /// The number of arguments in the splatted tuple. + arg_count: u16, + }, +} + +impl<'tcx> SplattedDef<'tcx> { + pub fn def_id(&self) -> Option { + match self { + SplattedDef::FnDef { def_id, .. } => Some(*def_id), + SplattedDef::NotAFnDef { .. } => None, + } + } + + pub fn arg_index(&self) -> u16 { + match self { + SplattedDef::FnDef { arg_index, .. } => *arg_index, + SplattedDef::NotAFnDef { arg_index, .. } => *arg_index, + } + } + + pub fn arg_count(&self) -> u16 { + match self { + SplattedDef::FnDef { arg_count, .. } => *arg_count, + SplattedDef::NotAFnDef { arg_count, .. } => *arg_count, + } + } } /// Validate that the given HirId (respectively its `local_id` part) can be diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index badce1d168138..0db6cdc2f9ca5 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -28,6 +28,36 @@ use tracing::{debug, info, instrument, trace}; use crate::diagnostics::*; use crate::thir::cx::ThirBuildCx; +/// The receiver of a splatted method, or the expression for a splatted function call. +#[derive(Copy, Clone, Debug)] +enum SplattedFunc<'tcx> { + /// The expression for a method receiver. Always a FnDef. + FnDefReceiver(&'tcx hir::Expr<'tcx>), + /// The expression or path for a function call. + /// This can be a FnDef or FnPtr. + FnExpression(&'tcx hir::Expr<'tcx>), +} + +impl<'tcx> SplattedFunc<'tcx> { + fn has_receiver(&self) -> bool { + matches!(self, SplattedFunc::FnDefReceiver(_)) + } + + fn receiver(&self) -> Option<&'tcx hir::Expr<'tcx>> { + match self { + SplattedFunc::FnDefReceiver(receiver) => Some(receiver), + SplattedFunc::FnExpression(_fn_expression) => None, + } + } + + fn fn_expression(&self) -> Option<&'tcx hir::Expr<'tcx>> { + match self { + SplattedFunc::FnDefReceiver(_receiver) => None, + SplattedFunc::FnExpression(fn_expression) => Some(fn_expression), + } + } +} + fn parsed_attrs(id: HirId, tcx: TyCtxt<'_>) -> ThinVec { HasAttrs::get_attrs(id, &tcx) .into_iter() @@ -375,7 +405,12 @@ impl<'tcx> ThirBuildCx<'tcx> { if self.typeck_results.is_splatted_call(expr) { // The callee has a splatted tuple argument. // rewrite `receiver.f(a, u, v)` into `receiver.f(a, #[rustc_splat] (u, v))` - self.convert_splatted_callee(expr, fn_span, args, Some(receiver)) + self.convert_splatted_callee( + expr, + fn_span, + args, + SplattedFunc::FnDefReceiver(receiver), + ) } else { // Rewrite a.b(c) into UFCS form like Trait::b(a, c) let expr = self.method_callee(expr, segment.ident.span, None); @@ -425,7 +460,12 @@ impl<'tcx> ThirBuildCx<'tcx> { } else if self.typeck_results.is_splatted_call(expr) { // The callee has a splatted tuple argument. // rewrite `f(a, u, v)` into `f(a, #[rustc_splat] (u, v))` - self.convert_splatted_callee(expr, fun.span, args, None) + self.convert_splatted_callee( + expr, + fun.span, + args, + SplattedFunc::FnExpression(fun), + ) } else { // Tuple-like ADTs are represented as ExprKind::Call. We convert them here. let adt_data = if let hir::ExprKind::Path(ref qpath) = fun.kind @@ -1232,7 +1272,7 @@ impl<'tcx> ThirBuildCx<'tcx> { call_expr: &'tcx hir::Expr<'_>, fn_span: Span, args: &'tcx [hir::Expr<'tcx>], - receiver: Option<&'tcx hir::Expr<'tcx>>, + receiver_or_func: SplattedFunc<'tcx>, ) -> ExprKind<'tcx> { let tcx = self.tcx; @@ -1242,19 +1282,19 @@ impl<'tcx> ThirBuildCx<'tcx> { span_bug!(call_expr.span, "no splatted def for function or method callee") }); - let tupled_arg_index = usize::from(splatted_def.arg_index); - let tupled_args_count = usize::from(splatted_def.arg_count); + let tupled_arg_index = usize::from(splatted_def.arg_index()); + let tupled_args_count = usize::from(splatted_def.arg_count()); // Splatting an empty tuple is permitted: `a.f() -> Trait::f(a, #[rustc_splat] ())`. // In that case, the tupled arg index is one past the end of the args. if tupled_arg_index + tupled_args_count > args.len() { span_bug!( call_expr.span, - "splatted arg index out of bounds of function args: {:?} + {:?} > {:?} for function call: receiver {:?}, args {:?}", + "splatted arg index out of bounds of function args: {:?} + {:?} > {:?} for function call: {:?}, args {:?}", tupled_arg_index, tupled_args_count, args.len(), - receiver, + receiver_or_func, args, ); } @@ -1291,23 +1331,23 @@ impl<'tcx> ThirBuildCx<'tcx> { // Handle the receiver as the first arg, if present let mut args = Vec::with_capacity( - usize::from(receiver.is_some()) + usize::from(receiver_or_func.has_receiver()) + initial_non_tupled_args.len() + 1 + final_non_tupled_args.len(), ); - if let Some(receiver) = receiver { + if let Some(receiver) = receiver_or_func.receiver() { args.push(self.mirror_expr(receiver)); } args.extend(initial_non_tupled_args); args.push(tupled_args); args.extend(final_non_tupled_args); - let fn_span = if receiver.is_some() { fn_span } else { call_expr.span }; + let fn_span = if receiver_or_func.has_receiver() { fn_span } else { call_expr.span }; - let (fn_ty, fun_expr) = match (splatted_def, receiver) { + let (fn_ty, fun_expr) = match (splatted_def, receiver_or_func.fn_expression()) { // Create a FnDef shim for user-provided types - (SplattedDef { def_id: Some(def_id), arg_index, arg_count }, _) => { + (SplattedDef::FnDef { def_id, arg_index, arg_count }, _) => { // We're calling a function via a FnDef, and its possibly generic type // This is effectively `self.method_callee(call_expr, fn_span, None)`, // applied to `splatted_def` instead of `type_dependent_def`. @@ -1332,7 +1372,7 @@ impl<'tcx> ThirBuildCx<'tcx> { }; (callee_expr.ty, self.thir.exprs.push(callee_expr)) } - (SplattedDef { def_id: None, .. }, _) => { + (SplattedDef::NotAFnDef { not_yet_implemented: _, .. }, _) => { span_bug!(call_expr.span, "splatted FnPtr side-tables are not yet implemented"); } }; From f88563cfee254af537e9a8301dcb8269ed1a5b8b Mon Sep 17 00:00:00 2001 From: teor Date: Tue, 28 Jul 2026 15:01:22 +1000 Subject: [PATCH 011/100] Make splatted FnPtr calls work (rather than ICE) Add tests for generic function pointers Change FnPtr tests to use assert_eq!() rather than println!() --- compiler/rustc_hir_typeck/src/callee.rs | 8 +- compiler/rustc_hir_typeck/src/expr.rs | 2 +- .../rustc_hir_typeck/src/fn_ctxt/_impl.rs | 20 ++- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 2 + .../rustc_middle/src/ty/typeck_results.rs | 20 ++- compiler/rustc_mir_build/src/thir/cx/expr.rs | 30 +++- tests/ui/splat/splat-fn-ptr-cast.rs | 5 +- tests/ui/splat/splat-fn-ptr-generic.rs | 58 ++++++++ tests/ui/splat/splat-fn-ptr-ptr-tuple.rs | 130 ++++++++++++++---- tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr | 24 ---- tests/ui/splat/splat-fn-ptr-tuple-const.rs | 24 +--- .../ui/splat/splat-fn-ptr-tuple-const.stderr | 46 +------ tests/ui/splat/splat-fn-ptr-tuple-fail.rs | 18 +++ .../splat/splat-fn-ptr-tuple-fail.run.stderr | 3 + tests/ui/splat/splat-fn-ptr-tuple.rs | 73 +++++----- tests/ui/splat/splat-fn-ptr-tuple.stderr | 23 ---- 16 files changed, 290 insertions(+), 196 deletions(-) create mode 100644 tests/ui/splat/splat-fn-ptr-generic.rs delete mode 100644 tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr create mode 100644 tests/ui/splat/splat-fn-ptr-tuple-fail.rs create mode 100644 tests/ui/splat/splat-fn-ptr-tuple-fail.run.stderr delete mode 100644 tests/ui/splat/splat-fn-ptr-tuple.stderr diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 0caac74f2e4e1..fdbaa1a9e2e57 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -37,8 +37,10 @@ pub(crate) enum SplatLoweringInfo<'tcx> { /// The DefId of the FnDef being called, used to look up the function type. /// Also used during argument suggestion for non-splatted function calls. FnDef(DefId), - /// FIXME(splat): Stub for non-FnDef - NotAFnDef(std::marker::PhantomData<&'tcx ()>), + /// The type of the FnPtr being called. + FnPtr(Ty<'tcx>), + /// Type resolution errored. + Error(ErrorGuaranteed), } /// Checks that it is legal to call methods of the trait corresponding @@ -613,7 +615,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // Splatted FnDefs use the DefId to look up the type, FnPtrs need it directly let fn_id = match def_id { Some(x) => SplatLoweringInfo::FnDef(x), - None => SplatLoweringInfo::NotAFnDef(std::marker::PhantomData), + None => SplatLoweringInfo::FnPtr(callee_ty), }; self.check_argument_types_maybe_method_like( diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 0e2720f4aa1d1..3909c736088b0 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -1511,7 +1511,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { args, false, TupleArgumentsFlag::DontTupleArguments, - SplatLoweringInfo::NotAFnDef(std::marker::PhantomData), + SplatLoweringInfo::Error(guar), Some(GenericArgsRef::default()), ); diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index edfe1ec00f5e7..aaa4e4e643bc9 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -283,16 +283,28 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.write_args(hir_id, callee_generic_args); } } - // FIXME(splat): handle FnPtrs - SplatLoweringInfo::NotAFnDef(_) => { + // We're splatting a FnPtr based on its type + SplatLoweringInfo::FnPtr(fn_ty) => { + // FIXME(splat): do we need to look up both these HirIds? + // They can be different (and are different in some UI tests) self.write_splatted_resolution( hir_id, - Ok(SplattedDef::NotAFnDef { - not_yet_implemented: std::marker::PhantomData, + Ok(SplattedDef::FnPtr { + fn_ptr_type: fn_ty, arg_index: first_tupled_arg_index, arg_count: tupled_args_count, }), ); + // FIXME(splat): is this actually populated and used correctly? + if let Some(callee_generic_args) = callee_generic_args { + self.write_args(hir_id, callee_generic_args); + } + } + SplatLoweringInfo::Error(guar) => { + self.write_splatted_resolution(hir_id, Err(guar)); + if let Some(callee_generic_args) = callee_generic_args { + self.write_args(hir_id, callee_generic_args); + } } } } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index cc614542015a2..005411915d17c 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -3513,6 +3513,8 @@ impl<'a, 'tcx> CallCtxt<'a, 'tcx> { format!("/* {} */", arg.name) } else { // FIXME(FnPtr, splat): What suggestions are needed for FnPtrs? + // SplatLoweringInfo::FnPtr(Ty) and SplatLoweringInfo::Error currently fall through to + // this placeholder "/* value */".to_string() } } diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs index cf447eaa5838c..ff7cef3613437 100644 --- a/compiler/rustc_middle/src/ty/typeck_results.rs +++ b/compiler/rustc_middle/src/ty/typeck_results.rs @@ -614,9 +614,10 @@ pub enum SplattedDef<'tcx> { arg_count: u16, }, - /// FIXME(splat): handle FnPtrs - NotAFnDef { - not_yet_implemented: std::marker::PhantomData<&'tcx ()>, + /// A resolved FnPtr Call. + FnPtr { + /// The resolved type of the FnPtr. + fn_ptr_type: Ty<'tcx>, /// The index of the first argument in the callee's splatted tuple, and the index of the /// splatted tuple argument in the caller. @@ -631,21 +632,28 @@ impl<'tcx> SplattedDef<'tcx> { pub fn def_id(&self) -> Option { match self { SplattedDef::FnDef { def_id, .. } => Some(*def_id), - SplattedDef::NotAFnDef { .. } => None, + SplattedDef::FnPtr { .. } => None, + } + } + + pub fn fn_ptr_type(&self) -> Option> { + match self { + SplattedDef::FnDef { .. } => None, + SplattedDef::FnPtr { fn_ptr_type, .. } => Some(*fn_ptr_type), } } pub fn arg_index(&self) -> u16 { match self { SplattedDef::FnDef { arg_index, .. } => *arg_index, - SplattedDef::NotAFnDef { arg_index, .. } => *arg_index, + SplattedDef::FnPtr { arg_index, .. } => *arg_index, } } pub fn arg_count(&self) -> u16 { match self { SplattedDef::FnDef { arg_count, .. } => *arg_count, - SplattedDef::NotAFnDef { arg_count, .. } => *arg_count, + SplattedDef::FnPtr { arg_count, .. } => *arg_count, } } } diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 0db6cdc2f9ca5..fcf2432b4d8dc 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -1372,8 +1372,34 @@ impl<'tcx> ThirBuildCx<'tcx> { }; (callee_expr.ty, self.thir.exprs.push(callee_expr)) } - (SplattedDef::NotAFnDef { not_yet_implemented: _, .. }, _) => { - span_bug!(call_expr.span, "splatted FnPtr side-tables are not yet implemented"); + + // We're calling a function via a FnPtr and its type + // FIXME(splat): do we need to populate and apply user_provided_types() ? + (SplattedDef::FnPtr { fn_ptr_type, arg_index, arg_count }, Some(fn_expression)) => { + debug!( + "splatted_callee FnPtr: fn_ty={:?} arg_index={:?} arg_count={:?}", + fn_ptr_type, arg_index, arg_count, + ); + + if !fn_ptr_type.is_fn() { + span_bug!( + call_expr.span, + "splatted FnPtr side-tables were not populated correctly, non-fn type received: {:?}", + fn_ptr_type + ) + } + + // Pass through the FnPtr type and the mirrored function path + (fn_ptr_type, self.mirror_expr(fn_expression)) + } + // FnPtrs must have a function expression (and they never have method receivers) + (SplattedDef::FnPtr { .. }, None) => { + span_bug!( + call_expr.span, + "convert_splatted_callee: FnPtr without fn expression (or with receiver) is invalid: splatted_def={:?}, receiver_or_func={:?}", + splatted_def, + receiver_or_func, + ); } }; diff --git a/tests/ui/splat/splat-fn-ptr-cast.rs b/tests/ui/splat/splat-fn-ptr-cast.rs index 6e4a05ac2a776..9b1eac8a6fa85 100644 --- a/tests/ui/splat/splat-fn-ptr-cast.rs +++ b/tests/ui/splat/splat-fn-ptr-cast.rs @@ -8,9 +8,8 @@ fn main() { // Bug #158603 regression test variants #[rustfmt::skip] - let _x: fn(#[rustc_splat] (f32,)) = None.unwrap(); - // FIXME(splat): causes an ICE until #158603 is fixed - //x(1.0); + let x: fn(#[rustc_splat] (f32,)) = None.unwrap(); + x(1.0); let x: fn((i32,)) = None.unwrap(); x((1,)); diff --git a/tests/ui/splat/splat-fn-ptr-generic.rs b/tests/ui/splat/splat-fn-ptr-generic.rs new file mode 100644 index 0000000000000..a41fb0aa2a43a --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-generic.rs @@ -0,0 +1,58 @@ +//! Test using `#[rustc_splat]` on tuple arguments of pointers to generic functions. +//@ run-pass + +#![expect(incomplete_features)] +#![feature(splat, tuple_trait)] + +use std::fmt::Debug; +use std::marker::Tuple; + +fn generic(#[rustc_splat] a: T) -> String { + format!("{a:?}") +} + +// FIXME(rustfmt): the attribute gets deleted by rustfmt +#[rustfmt::skip] +fn main() { + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> String + = generic as fn(#[rustc_splat] (u32, i8)) -> String; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> String + = generic::<(u32, i8)> as fn(#[rustc_splat] (u32, i8)) -> String; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr = generic as fn(#[rustc_splat] (u32, i8)) -> String; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr = generic::<(u32, i8)> as fn(#[rustc_splat] (u32, i8)) -> String; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> String = generic as _; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> String = generic::<(u32, i8)> as _; + assert_eq!(fn_ptr(1, -2), "(1, -2)"); + assert_eq!(fn_ptr(1u32, -2i8), "(1, -2)"); + + // Now without explicit `as`, this requires turbofish + let fn_ptr: fn(#[rustc_splat] (f64, i8)) -> String = generic::<(f64, i8)>; + assert_eq!(fn_ptr(3.5, -2), "(3.5, -2)"); + assert_eq!(fn_ptr(3.5f64, -2i8), "(3.5, -2)"); + + // FIXME(unused_variables): This is obviously used + #[expect(unused_variables)] + let fn_ptr = generic; + assert_eq!(fn_ptr(-1, 2, 3.5), "(-1, 2, 3.5)"); + assert_eq!(fn_ptr(-1i8, 2u32, 3.5f64), "(-1, 2, 3.5)"); + + #[expect(unused_variables)] + let fn_ptr = generic::<(i8, u32, f64)>; + assert_eq!(fn_ptr(-1, 2, 3.5), "(-1, 2, 3.5)"); + assert_eq!(fn_ptr(-1i8, 2u32, 3.5f64), "(-1, 2, 3.5)"); +} diff --git a/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs b/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs index fbe2d8c192f73..6473abce4b750 100644 --- a/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs +++ b/tests/ui/splat/splat-fn-ptr-ptr-tuple.rs @@ -1,43 +1,113 @@ //! Test using `#[rustc_splat]` on tuple arguments of pointers to pointers to simple functions. -//! Currently ICEs, but if we fix it, we'll want to know and update this test to pass. +//! Bug #158603 regression test +//@ run-pass -//@ failure-status: 101 - -//@ normalize-stderr: ".*error:.*compiler/([^:]+):\d{1,}:\d{1,}:(.*)" -> "error: compiler/$1:LL:CC:$2" -//@ normalize-stderr: "thread.*panicked at .*compiler.*" -> "" -//@ normalize-stderr: "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}" -//@ normalize-stderr: "note: compiler flags.*\n\n" -> "" -//@ normalize-stderr: " +\d{1,}: .*\n" -> "" -//@ normalize-stderr: " + at .*\n" -> "" -//@ normalize-stderr: ".*omitted \d{1,} frames?.*\n" -> "" -//@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" -//@ normalize-stderr: ".*--> .*/splat-fn-ptr-tuple.rs:\d{1,}:\d{1,}.*\n" -> "" - -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(splat)] -fn tuple_args(#[rustc_splat] (_a, _b): (u32, i8)) {} +use std::ptr; + +fn tuple_args(#[rustc_splat] (a, b): (u32, i8)) -> (i8, u32) { + // Permute the returned values as a codegen test + (b, a) +} -fn splat_non_terminal_arg(#[rustc_splat] (_a, _b): (u32, i8), _c: f64) {} +fn splat_non_terminal_arg(#[rustc_splat] (a, b): (u32, i8), c: f64) -> (i8, f64, u32) { + // Permute the returned values as a codegen test + (b, c, a) +} +// FIXME(rustfmt): the attribute gets deleted by rustfmt +#[rustfmt::skip] fn main() { - // FIXME(splat): not currently supported, can be supported when we no longer require a DefId in - // MIR lowering - // FIXME(rustfmt): the attribute gets deleted by rustfmt - #[rustfmt::skip] - let fn_pp: *const fn(#[rustc_splat] (u32, i8)) - = tuple_args as *const fn(#[rustc_splat] (u32, i8)); + let fn_pp: &fn(#[rustc_splat] (u32, i8)) -> (i8, u32) + = &(tuple_args as fn(#[rustc_splat] (u32, i8)) -> (i8, u32)); + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + let fn_pp: &fn(#[rustc_splat] (u32, i8)) -> (i8, u32) = &(tuple_args as _); + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + let fn_pp = &(tuple_args as fn(#[rustc_splat] (u32, i8)) -> (i8, u32)); + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + // FIXME(unused_variables): This is obviously used + #[expect(unused_variables)] + let fn_pp = &tuple_args; + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + // Now with *const + let fn_pp: *const fn(#[rustc_splat] (u32, i8)) -> (i8, u32) + = ptr::from_ref(&(tuple_args as fn(#[rustc_splat] (u32, i8)) -> (i8, u32))); + unsafe { + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + } + + let fn_pp: *const fn(#[rustc_splat] (u32, i8)) -> (i8, u32) = ptr::from_ref(&(tuple_args as _)); + unsafe { + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + } + + let fn_pp = ptr::from_ref(&(tuple_args as fn(#[rustc_splat] (u32, i8)) -> (i8, u32))); + unsafe { + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + } + + #[expect(unused_variables)] + let fn_pp = ptr::from_ref(&tuple_args); + // FIXME(unsafe): dereferencing *const should require unsafe + assert_eq!((*fn_pp)(1, 2), (2, 1)); + assert_eq!((*fn_pp)(1u32, 2i8), (2i8, 1u32)); + + // Now with *mut and non-terminal splat + let fn_pp: *mut fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32) + = ptr::from_mut( + &mut (splat_non_terminal_arg as fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32)) + ); + unsafe { + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); + } + + let fn_pp: *mut fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32) + = ptr::from_mut(&mut (splat_non_terminal_arg as _)); + unsafe { + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); + } + + let fn_pp = ptr::from_mut( + &mut (splat_non_terminal_arg as fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32)) + ); + unsafe { + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); + } + + #[expect(unused_variables)] + let fn_pp = ptr::from_mut(&mut splat_non_terminal_arg); + // FIXME(unsafe): dereferencing *mut should require unsafe + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); + + // Now with & as *const and non-terminal splat + let fn_pp: *const fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32) + = &(splat_non_terminal_arg as fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32)); unsafe { - (*fn_pp)(1, 2); //~ ERROR splatted FnPtr side-tables are not yet implemented - // The ICE means that code after this line is not fully checked - (*fn_pp)(1u32, 2i8); + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); } - #[rustfmt::skip] - let fn_pp: *const fn(#[rustc_splat] (u32, i8), f64) = - splat_non_terminal_arg as *const fn(#[rustc_splat] (u32, i8), f64); + let fn_pp: *const fn(#[rustc_splat] (u32, i8), f64) -> (i8, f64, u32) + = &(splat_non_terminal_arg as _); unsafe { - (*fn_pp)(1, 2, 3.5); - (*fn_pp)(1u32, 2i8, 3.5f64); + assert_eq!((*fn_pp)(1, 2, 3.5), (2, 3.5, 1)); + assert_eq!((*fn_pp)(1u32, 2i8, 3.5f64), (2i8, 3.5f64, 1u32)); } } diff --git a/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr b/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr deleted file mode 100644 index fd9fce68eb255..0000000000000 --- a/tests/ui/splat/splat-fn-ptr-ptr-tuple.stderr +++ /dev/null @@ -1,24 +0,0 @@ -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented - --> $DIR/splat-fn-ptr-ptr-tuple.rs:31:9 - | -LL | (*fn_pp)(1, 2); - | ^^^^^^^^^^^^^^ - - - -Box -stack backtrace: - -note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md - -note: please make sure that you have updated to the latest nightly - -note: rustc {version} running on {platform} - -query stack during panic: -#0 [thir_body] building THIR for `main` -#1 [check_unsafety] unsafety-checking `main` -#2 [analysis] running analysis passes on crate `splat_fn_ptr_ptr_tuple` -end of query stack -error: aborting due to 1 previous error - diff --git a/tests/ui/splat/splat-fn-ptr-tuple-const.rs b/tests/ui/splat/splat-fn-ptr-tuple-const.rs index 035c4db9ad5be..c95c20fc89772 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple-const.rs +++ b/tests/ui/splat/splat-fn-ptr-tuple-const.rs @@ -1,17 +1,4 @@ //! Test using `#[rustc_splat]` on tuple arguments of generic function constants. -//! Currently ICEs (#158603), but if we fix it, we'll want to know and update this test to pass. - -//@ failure-status: 101 - -//@ normalize-stderr: ".*error:.*compiler/([^:]+):\d{1,}:\d{1,}:(.*)" -> "error: compiler/$1:LL:CC:$2" -//@ normalize-stderr: "thread.*panicked at .*compiler.*" -> "" -//@ normalize-stderr: "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}" -//@ normalize-stderr: "note: compiler flags.*\n\n" -> "" -//@ normalize-stderr: " +\d{1,}: .*\n" -> "" -//@ normalize-stderr: " + at .*\n" -> "" -//@ normalize-stderr: ".*omitted \d{1,} frames?.*\n" -> "" -//@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" -//@ normalize-stderr: ".*--> .*/splat-fn-ptr-tuple.rs:\d{1,}:\d{1,}.*\n" -> "" #![allow(incomplete_features)] #![feature(splat, tuple_trait)] @@ -20,15 +7,12 @@ use std::marker::Tuple; fn f(#[rustc_splat] args: Args) {} +// FIXME(rustfmt): the attribute gets deleted by rustfmt +#[rustfmt::skip] fn main() { - // FIXME(splat): not currently supported, can be supported when we no longer require a DefId in - // MIR lowering - // FIXME(rustfmt): the attribute gets deleted by rustfmt - #[rustfmt::skip] const F2: fn(#[rustc_splat] (u8, u32)) = f::<(u8, u32)>; - const R2: () = F2(1, 2); //~ ERROR splatted FnPtr side-tables are not yet implemented + const R2: () = F2(1, 2); //~ ERROR function pointer calls are not allowed in constants - #[rustfmt::skip] const F1: fn(#[rustc_splat] ((u8, u32),)) = f::<((u8, u32),)>; - const R1: () = F1((1, 2)); //~ ERROR splatted FnPtr side-tables are not yet implemented + const R1: () = F1((1, 2)); //~ ERROR function pointer calls are not allowed in constants } diff --git a/tests/ui/splat/splat-fn-ptr-tuple-const.stderr b/tests/ui/splat/splat-fn-ptr-tuple-const.stderr index 1767782a9535e..f4b033445b3b2 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple-const.stderr +++ b/tests/ui/splat/splat-fn-ptr-tuple-const.stderr @@ -1,52 +1,14 @@ -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented - --> $DIR/splat-fn-ptr-tuple-const.rs:29:20 +error: function pointer calls are not allowed in constants + --> $DIR/splat-fn-ptr-tuple-const.rs:14:20 | LL | const R2: () = F2(1, 2); | ^^^^^^^^ - - -Box -stack backtrace: - -note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md - -note: please make sure that you have updated to the latest nightly - -note: rustc {version} running on {platform} - -query stack during panic: -#0 [thir_body] building THIR for `main::R2` -#1 [check_match] match-checking `main::R2` -#2 [mir_built] building MIR for `main::R2` -#3 [trivial_const] checking if `main::R2` is a trivial const -#4 [eval_to_const_value_raw] simplifying constant for the type system `main::R2` -#5 [analysis] running analysis passes on crate `splat_fn_ptr_tuple_const` -end of query stack -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented - --> $DIR/splat-fn-ptr-tuple-const.rs:33:20 +error: function pointer calls are not allowed in constants + --> $DIR/splat-fn-ptr-tuple-const.rs:17:20 | LL | const R1: () = F1((1, 2)); | ^^^^^^^^^^ - - -Box -stack backtrace: - -note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md - -note: please make sure that you have updated to the latest nightly - -note: rustc {version} running on {platform} - -query stack during panic: -#0 [thir_body] building THIR for `main::R1` -#1 [check_match] match-checking `main::R1` -#2 [mir_built] building MIR for `main::R1` -#3 [trivial_const] checking if `main::R1` is a trivial const -#4 [eval_to_const_value_raw] simplifying constant for the type system `main::R1` -#5 [analysis] running analysis passes on crate `splat_fn_ptr_tuple_const` -end of query stack error: aborting due to 2 previous errors diff --git a/tests/ui/splat/splat-fn-ptr-tuple-fail.rs b/tests/ui/splat/splat-fn-ptr-tuple-fail.rs new file mode 100644 index 0000000000000..a76f9f30cae32 --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-tuple-fail.rs @@ -0,0 +1,18 @@ +//! Test using `#[rustc_splat]` on tuple arguments of pointers to invalid simple functions. +//! Bug #158603 regression test +//@ run-fail +//@ check-run-results +//@ exec-env: RUST_BACKTRACE=0 + +//@ normalize-stderr: "thread '.*'" -> "thread 'NAME'" +//@ normalize-stderr: "note: run with.*\n" -> "" + +#![expect(incomplete_features)] +#![feature(splat)] + +fn main() { + // FIXME(rustfmt): the attribute gets deleted by rustfmt + #[rustfmt::skip] + let x: fn(#[rustc_splat] (i32,)) = None.unwrap(); + x(1); +} diff --git a/tests/ui/splat/splat-fn-ptr-tuple-fail.run.stderr b/tests/ui/splat/splat-fn-ptr-tuple-fail.run.stderr new file mode 100644 index 0000000000000..7536f99c69bd6 --- /dev/null +++ b/tests/ui/splat/splat-fn-ptr-tuple-fail.run.stderr @@ -0,0 +1,3 @@ + +thread 'NAME' ($TID) panicked at $DIR/splat-fn-ptr-tuple-fail.rs:16:45: +called `Option::unwrap()` on a `None` value diff --git a/tests/ui/splat/splat-fn-ptr-tuple.rs b/tests/ui/splat/splat-fn-ptr-tuple.rs index 7fb06ad1c6bc1..23690865e9aaf 100644 --- a/tests/ui/splat/splat-fn-ptr-tuple.rs +++ b/tests/ui/splat/splat-fn-ptr-tuple.rs @@ -1,46 +1,43 @@ //! Test using `#[rustc_splat]` on tuple arguments of pointers to simple functions. -//! Currently ICEs, but if we fix it, we'll want to know and update this test to pass. +//! Bug #158603 regression test +//@ run-pass -//@ failure-status: 101 - -//@ normalize-stderr: ".*error:.*compiler/([^:]+):\d{1,}:\d{1,}:(.*)" -> "error: compiler/$1:LL:CC:$2" -//@ normalize-stderr: "thread.*panicked at .*compiler.*" -> "" -//@ normalize-stderr: "note: rustc.*running on.*" -> "note: rustc {version} running on {platform}" -//@ normalize-stderr: "note: compiler flags.*\n\n" -> "" -//@ normalize-stderr: " +\d{1,}: .*\n" -> "" -//@ normalize-stderr: " + at .*\n" -> "" -//@ normalize-stderr: ".*omitted \d{1,} frames?.*\n" -> "" -//@ normalize-stderr: ".*note: Some details are omitted.*\n" -> "" -//@ normalize-stderr: ".*--> .*/splat-fn-ptr-tuple.rs:\d{1,}:\d{1,}.*\n" -> "" - -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(splat)] -fn tuple_args(#[rustc_splat] (_a, _b): (u32, i8)) {} +fn tuple_args(#[rustc_splat] (a, b): (u32, i8)) -> (u32, i8) { + (a, b) +} -fn splat_non_terminal_arg(#[rustc_splat] (_a, _b): (u32, i8), _c: f64) {} +fn splat_non_terminal_arg(#[rustc_splat] (a, b): (u32, i8), c: f64) -> (f64, i8, u32) { + // Permute the returned values as a codegen test + (c, b, a) +} +// FIXME(rustfmt): the attribute gets deleted by rustfmt +#[rustfmt::skip] fn main() { - // FIXME(splat): not currently supported, can be supported when we no longer require a DefId in - // MIR lowering - // FIXME(rustfmt): the attribute gets deleted by rustfmt - #[rustfmt::skip] - let fn_ptr: fn(#[rustc_splat] (u32, i8)) = tuple_args; - fn_ptr(1, 2); //~ ERROR splatted FnPtr side-tables are not yet implemented - // The ICE means that code after this line is not fully checked - fn_ptr(1u32, 2i8); - - // FIXME(splat): should splatted functions be callable with tupled and un-tupled arguments? - // Add a tupled test for each call if they are. - //fn_ptr((1, 2)); // ERROR this splatted function takes 2 arguments, but 1 was provided - - #[rustfmt::skip] - let fn_ptr: fn(#[rustc_splat] (u32, i8), f64) = splat_non_terminal_arg; - fn_ptr(1, 2, 3.5); - fn_ptr(1u32, 2i8, 3.5f64); - - // Bug #158603 regression test - #[rustfmt::skip] - let x: fn(#[rustc_splat] (i32,)) = None.unwrap(); - x(1); + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> (u32, i8) + = tuple_args as fn(#[rustc_splat] (u32, i8)) -> (u32, i8); + assert_eq!(fn_ptr(1, 2), (1, 2)); + assert_eq!(fn_ptr(1u32, 2i8), (1u32, 2i8)); + + let fn_ptr = tuple_args as fn(#[rustc_splat] (u32, i8)) -> (u32, i8); + assert_eq!(fn_ptr(1, 2), (1, 2)); + assert_eq!(fn_ptr(1u32, 2i8), (1u32, 2i8)); + + let fn_ptr: fn(#[rustc_splat] (u32, i8)) -> (u32, i8) = tuple_args as _; + assert_eq!(fn_ptr(1, 2), (1, 2)); + assert_eq!(fn_ptr(1u32, 2i8), (1u32, 2i8)); + + // Now without explicit `as` + let fn_ptr: fn(#[rustc_splat] (u32, i8), f64) -> (f64, i8, u32) = splat_non_terminal_arg; + assert_eq!(fn_ptr(1, 2, 3.5), (3.5, 2, 1)); + assert_eq!(fn_ptr(1u32, 2i8, 3.5f64), (3.5f64, 2i8, 1u32)); + + // FIXME(unused_variables): This is obviously used + #[expect(unused_variables)] + let fn_ptr = splat_non_terminal_arg; + assert_eq!(fn_ptr(1, 2, 3.5), (3.5, 2, 1)); + assert_eq!(fn_ptr(1u32, 2i8, 3.5f64), (3.5f64, 2i8, 1u32)); } diff --git a/tests/ui/splat/splat-fn-ptr-tuple.stderr b/tests/ui/splat/splat-fn-ptr-tuple.stderr deleted file mode 100644 index 4cc861cafe968..0000000000000 --- a/tests/ui/splat/splat-fn-ptr-tuple.stderr +++ /dev/null @@ -1,23 +0,0 @@ -error: compiler/rustc_mir_build/src/thir/cx/expr.rs:LL:CC: splatted FnPtr side-tables are not yet implemented - | -LL | fn_ptr(1, 2); - | ^^^^^^^^^^^^ - - - -Box -stack backtrace: - -note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md - -note: please make sure that you have updated to the latest nightly - -note: rustc {version} running on {platform} - -query stack during panic: -#0 [thir_body] building THIR for `main` -#1 [check_unsafety] unsafety-checking `main` -#2 [analysis] running analysis passes on crate `splat_fn_ptr_tuple` -end of query stack -error: aborting due to 1 previous error - From f3e21534d4bcb5209af4a649386822919849a9d5 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Mon, 3 Aug 2026 12:22:24 +0200 Subject: [PATCH 012/100] don't force intronsic results into memory --- src/tools/miri/src/intrinsics/math.rs | 8 ++++---- src/tools/miri/src/intrinsics/mod.rs | 14 +++++--------- src/tools/miri/src/intrinsics/simd.rs | 2 +- src/tools/miri/src/math.rs | 4 ++-- src/tools/miri/src/shims/foreign_items.rs | 2 +- src/tools/miri/src/shims/mod.rs | 4 ++-- src/tools/miri/src/shims/unwind.rs | 7 ++++--- 7 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/tools/miri/src/intrinsics/math.rs b/src/tools/miri/src/intrinsics/math.rs index adb768e6bcffc..ad3881b0e6a6d 100644 --- a/src/tools/miri/src/intrinsics/math.rs +++ b/src/tools/miri/src/intrinsics/math.rs @@ -10,7 +10,7 @@ use crate::*; fn sqrt<'tcx, F: Float + FloatConvert + Into>( this: &mut MiriInterpCx<'tcx>, args: &[OpTy<'tcx>], - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ) -> InterpResult<'tcx> { let [f] = check_intrinsic_arg_count(args)?; math::sqrt_op::(this, f, dest) @@ -45,7 +45,7 @@ fn is_host_unary_float_op(intrinsic_name: &str) -> Option<(FloatTy, HostUnaryFlo fn pow_intrinsic<'tcx, S: Semantics>( this: &mut MiriInterpCx<'tcx>, args: &[OpTy<'tcx>], - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> where IeeeFloat: HostFloatOperation + IeeeExt + Float + Into, @@ -69,7 +69,7 @@ where fn powi_intrinsic<'tcx, S: Semantics>( this: &mut MiriInterpCx<'tcx>, args: &[OpTy<'tcx>], - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ) -> InterpResult<'tcx, ()> where IeeeFloat: HostFloatOperation + IeeeExt + Float + Into, @@ -98,7 +98,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { intrinsic_name: &str, _generic_args: ty::GenericArgsRef<'tcx>, args: &[OpTy<'tcx>], - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs index 0f55009db790b..7d7081fb609fb 100644 --- a/src/tools/miri/src/intrinsics/mod.rs +++ b/src/tools/miri/src/intrinsics/mod.rs @@ -53,12 +53,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let intrinsic_name = this.tcx.item_name(instance.def_id()); let intrinsic_name = intrinsic_name.as_str(); - // FIXME: avoid allocating memory - let dest = this.force_allocation(dest)?; - - let res = - this.emulate_intrinsic_by_name(intrinsic_name, instance.args, args, &dest, ret)?; - res.jump_to_next_block(this, &dest, ret, Some(unwind), |this| { + let res = this.emulate_intrinsic_by_name(intrinsic_name, instance.args, args, dest, ret)?; + res.jump_to_next_block(this, dest, ret, Some(unwind), |this| { // We haven't handled the intrinsic, let's see if we can use a fallback body. if this.tcx.intrinsic(instance.def_id()).unwrap().must_be_overridden { throw_unsup_format!("unimplemented intrinsic: `{intrinsic_name}`") @@ -88,7 +84,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { intrinsic_name: &str, generic_args: ty::GenericArgsRef<'tcx>, args: &[OpTy<'tcx>], - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ret: Option, ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); @@ -165,7 +161,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let link_name = this.tcx.codegen_fn_attrs(instance.def_id()).symbol_name.unwrap(); - // FIXME: avoid allocating memory + // These are anyway mostly vector intrinsics and vectors live in memory. let dest = this.force_allocation(dest)?; let res = 'handled: { @@ -250,7 +246,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }; // The rest either implements the logic, or falls back to `lookup_exported_symbol`. - res.jump_to_next_block(this, &dest, ret, None, |this| { + res.jump_to_next_block(this, &dest.clone().into(), ret, None, |this| { throw_machine_stop!(TerminationInfo::UnsupportedForeignItem(format!( "can't call LLVM intrinsic `{link_name}` on architecture `{arch}`", arch = this.tcx.sess.target.arch, diff --git a/src/tools/miri/src/intrinsics/simd.rs b/src/tools/miri/src/intrinsics/simd.rs index 74582bc58900e..1f2fd9a8a64df 100644 --- a/src/tools/miri/src/intrinsics/simd.rs +++ b/src/tools/miri/src/intrinsics/simd.rs @@ -14,7 +14,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &mut self, intrinsic_name: &str, args: &[OpTy<'tcx>], - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ) -> InterpResult<'tcx, EmulateItemResult> { let this = self.eval_context_mut(); match intrinsic_name { diff --git a/src/tools/miri/src/math.rs b/src/tools/miri/src/math.rs index f67831839b711..1cacc9dde86f8 100644 --- a/src/tools/miri/src/math.rs +++ b/src/tools/miri/src/math.rs @@ -462,7 +462,7 @@ pub(crate) fn sqrt(x: F) -> F { pub fn sqrt_op<'tcx, F: Float + FloatConvert + Into>( this: &mut MiriInterpCx<'tcx>, f: &OpTy<'tcx>, - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ) -> InterpResult<'tcx> { let f: F = this.read_scalar(f)?.to_float()?; // Sqrt is specified to be fully precise. @@ -536,7 +536,7 @@ pub fn host_unary_float_op<'tcx, S: Semantics>( this: &mut MiriInterpCx<'tcx>, f: &OpTy<'tcx>, op: HostUnaryFloatOp, - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ) -> InterpResult<'tcx> where IeeeFloat: HostFloatOperation + IeeeExt + Float + Into, diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 683e9095f9b0c..a904116017876 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -74,7 +74,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // The rest either implements the logic, or falls back to `lookup_exported_symbol`. let res = this.emulate_foreign_item_inner(link_name, abi, args, &dest)?; - res.jump_to_next_block(this, &dest, ret, Some(unwind), |this| { + res.jump_to_next_block(this, &dest.clone().into(), ret, Some(unwind), |this| { if let Some(body) = this.lookup_exported_symbol(link_name)? { return interp_ok(Some(body)); } diff --git a/src/tools/miri/src/shims/mod.rs b/src/tools/miri/src/shims/mod.rs index 56466b5f3a1f7..a41f1a5c8ec42 100644 --- a/src/tools/miri/src/shims/mod.rs +++ b/src/tools/miri/src/shims/mod.rs @@ -43,7 +43,7 @@ impl EmulateItemResult { pub fn jump_to_next_block<'tcx, T: Default>( self, ecx: &mut crate::MiriInterpCx<'tcx>, - dest: &crate::MPlaceTy<'tcx>, + dest: &crate::PlaceTy<'tcx>, ret: Option, unwind: Option, not_supported: impl FnOnce(&mut crate::MiriInterpCx<'tcx>) -> crate::InterpResult<'tcx, T>, @@ -52,7 +52,7 @@ impl EmulateItemResult { match self { EmulateItemResult::NeedsReturn => { - trace!("{:?}", ecx.dump_place(&dest.clone().into())); + trace!("{:?}", ecx.dump_place(dest)); ecx.return_to_block(ret)?; interp_ok(T::default()) } diff --git a/src/tools/miri/src/shims/unwind.rs b/src/tools/miri/src/shims/unwind.rs index e8a804a8b023b..820a78725eedc 100644 --- a/src/tools/miri/src/shims/unwind.rs +++ b/src/tools/miri/src/shims/unwind.rs @@ -62,7 +62,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { try_fn: &OpTy<'tcx>, data: &OpTy<'tcx>, catch_fn: &OpTy<'tcx>, - dest: &MPlaceTy<'tcx>, + dest: &PlaceTy<'tcx>, ret: Option, ) -> InterpResult<'tcx> { let this = self.eval_context_mut(); @@ -82,6 +82,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let try_fn = this.read_pointer(try_fn)?; let data = this.read_immediate(data)?; let catch_fn = this.read_pointer(catch_fn)?; + let dest = this.force_allocation(dest)?; // needs to be valid across fn calls // Now we make a function call, and pass `data` as first and only argument. let f_instance = this.get_ptr_fn(try_fn)?.as_instance()?; @@ -97,14 +98,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; // We ourselves will return `0`, eventually (will be overwritten if we catch a panic). - this.write_null(dest)?; + this.write_null(&dest)?; // In unwind mode, we tag this frame with the extra data needed to catch unwinding. // This lets `handle_stack_pop` (below) know that we should stop unwinding // when we pop this frame. if this.tcx.sess.panic_strategy() == PanicStrategy::Unwind { this.frame_mut().extra.catch_unwind = - Some(CatchUnwindData { catch_fn, data, dest: dest.clone(), ret }); + Some(CatchUnwindData { catch_fn, data, dest, ret }); } interp_ok(()) From 9ff75a767e1c5b0e0d666483f743e7a9a26e9b61 Mon Sep 17 00:00:00 2001 From: lcnr Date: Mon, 3 Aug 2026 20:04:21 +0200 Subject: [PATCH 013/100] make `DefiningTy` independent of borrowck --- .../rustc_borrowck/src/universal_regions.rs | 449 +++++++++--------- 1 file changed, 219 insertions(+), 230 deletions(-) diff --git a/compiler/rustc_borrowck/src/universal_regions.rs b/compiler/rustc_borrowck/src/universal_regions.rs index 2d4d98d812c65..694f29b942e4f 100644 --- a/compiler/rustc_borrowck/src/universal_regions.rs +++ b/compiler/rustc_borrowck/src/universal_regions.rs @@ -134,6 +134,204 @@ pub(crate) enum DefiningTy<'tcx> { } impl<'tcx> DefiningTy<'tcx> { + #[instrument(level = "debug", skip(tcx), ret)] + fn new(tcx: TyCtxt<'tcx>, body_def_id: LocalDefId) -> DefiningTy<'tcx> { + match tcx.hir_body_owner_kind(body_def_id) { + BodyOwnerKind::Closure | BodyOwnerKind::Fn => { + let defining_ty = tcx.type_of(body_def_id).instantiate_identity().skip_norm_wip(); + match *defining_ty.kind() { + ty::Closure(def_id, args) => DefiningTy::Closure(def_id, args), + ty::Coroutine(def_id, args) => DefiningTy::Coroutine(def_id, args), + ty::CoroutineClosure(def_id, args) => { + DefiningTy::CoroutineClosure(def_id, args) + } + ty::FnDef(def_id, args) => { + DefiningTy::FnDef(def_id, args.no_bound_vars().unwrap()) + } + _ => span_bug!( + tcx.def_span(body_def_id), + "expected defining type for `{body_def_id:?}`: `{defining_ty:?}`", + ), + } + } + + BodyOwnerKind::Const { .. } | BodyOwnerKind::Static(..) => { + match tcx.def_kind(body_def_id) { + DefKind::AnonConst + if tcx.anon_const_kind(body_def_id) + == ty::AnonConstKind::NonTypeSystemInline => + { + // This is required for `AscribeUserType` canonical query, which will call + // `type_of(inline_const_def_id)`. That `type_of` would inject erased lifetimes + // into borrowck, which is ICE #78174. + // + // As a workaround, inline consts have an additional generic param (`ty` + // below), so that `type_of(inline_const_def_id).substs(substs)` uses the + // proper type with NLL infer vars. + // + // Fetch the actual type from MIR, as `type_of` returns something useless + // like ``. + let body = tcx.mir_promoted(body_def_id).0.borrow(); + let ty = body.local_decls[RETURN_PLACE].ty; + let typeck_root_def_id = tcx.typeck_root_def_id(body_def_id.to_def_id()); + let parent_args = GenericArgs::identity_for_item(tcx, typeck_root_def_id); + let args = + InlineConstArgs::new(tcx, InlineConstArgsParts { parent_args, ty }) + .args; + DefiningTy::InlineConst(body_def_id.to_def_id(), args) + } + _ => { + let args = GenericArgs::identity_for_item(tcx, body_def_id.to_def_id()); + DefiningTy::Const(body_def_id.to_def_id(), args) + } + } + } + + BodyOwnerKind::GlobalAsm => DefiningTy::GlobalAsm(body_def_id.to_def_id()), + } + } + + #[instrument(level = "debug", skip(tcx, c_variadic_region), ret)] + fn inputs_and_output( + self, + tcx: TyCtxt<'tcx>, + c_variadic_region: impl FnOnce() -> ty::Region<'tcx>, + ) -> ty::Binder<'tcx, &'tcx ty::List>> { + match self { + DefiningTy::Closure(def_id, args) => { + let closure_sig = args.as_closure().sig(); + let inputs_and_output = closure_sig.inputs_and_output(); + let bound_vars = tcx.mk_bound_variable_kinds_from_iter( + inputs_and_output.bound_vars().iter().chain(iter::once( + ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv), + )), + ); + let br = ty::BoundRegion { + var: ty::BoundVar::from_usize(bound_vars.len() - 1), + kind: ty::BoundRegionKind::ClosureEnv, + }; + let env_region = ty::Region::new_bound(tcx, ty::INNERMOST, br); + let closure_ty = tcx.closure_env_ty( + Ty::new_closure(tcx, def_id, args), + args.as_closure().kind(), + env_region, + ); + + // The "inputs" of the closure in the + // signature appear as a tuple. The MIR side + // flattens this tuple. + let (&output, tuplized_inputs) = + inputs_and_output.skip_binder().split_last().unwrap(); + assert_eq!(tuplized_inputs.len(), 1, "multiple closure inputs"); + let &ty::Tuple(inputs) = tuplized_inputs[0].kind() else { + bug!("closure inputs not a tuple: {:?}", tuplized_inputs[0]); + }; + + ty::Binder::bind_with_vars( + tcx.mk_type_list_from_iter( + iter::once(closure_ty).chain(inputs).chain(iter::once(output)), + ), + bound_vars, + ) + } + + DefiningTy::Coroutine(def_id, args) => { + let resume_ty = args.as_coroutine().resume_ty(); + let output = args.as_coroutine().return_ty(); + let coroutine_ty = Ty::new_coroutine(tcx, def_id, args); + let inputs_and_output = tcx.mk_type_list(&[coroutine_ty, resume_ty, output]); + ty::Binder::dummy(inputs_and_output) + } + + // Construct the signature of the CoroutineClosure for the purposes of borrowck. + // This is pretty straightforward -- we: + // 1. first grab the `coroutine_closure_sig`, + // 2. compute the self type (`&`/`&mut`/no borrow), + // 3. flatten the tupled_input_tys, + // 4. construct the correct generator type to return with + // `CoroutineClosureSignature::to_coroutine_given_kind_and_upvars`. + // Then we wrap it all up into a list of inputs and output. + DefiningTy::CoroutineClosure(def_id, args) => { + let closure_sig = args.as_coroutine_closure().coroutine_closure_sig(); + let bound_vars = + tcx.mk_bound_variable_kinds_from_iter(closure_sig.bound_vars().iter().chain( + iter::once(ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv)), + )); + let br = ty::BoundRegion { + var: ty::BoundVar::from_usize(bound_vars.len() - 1), + kind: ty::BoundRegionKind::ClosureEnv, + }; + let env_region = ty::Region::new_bound(tcx, ty::INNERMOST, br); + let closure_kind = args.as_coroutine_closure().kind(); + + let closure_ty = tcx.closure_env_ty( + Ty::new_coroutine_closure(tcx, def_id, args), + closure_kind, + env_region, + ); + + let inputs = closure_sig.skip_binder().tupled_inputs_ty.tuple_fields(); + let output = closure_sig.skip_binder().to_coroutine_given_kind_and_upvars( + tcx, + args.as_coroutine_closure().parent_args(), + tcx.coroutine_for_closure(def_id), + closure_kind, + env_region, + args.as_coroutine_closure().tupled_upvars_ty(), + args.as_coroutine_closure().coroutine_captures_by_ref_ty(), + ); + + ty::Binder::bind_with_vars( + tcx.mk_type_list_from_iter( + iter::once(closure_ty).chain(inputs).chain(iter::once(output)), + ), + bound_vars, + ) + } + + DefiningTy::FnDef(def_id, _) => { + let sig = tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); + let inputs_and_output = sig.inputs_and_output(); + + // C-variadic fns also have a `VaList` input that's not listed in the signature + // (as it's created inside the body itself, not passed in from outside). + if tcx.fn_sig(def_id).skip_binder().c_variadic() { + let va_list_did = tcx.require_lang_item(LangItem::VaList, tcx.def_span(def_id)); + + let region = c_variadic_region(); + let va_list_ty = + tcx.type_of(va_list_did).instantiate(tcx, &[region.into()]).skip_norm_wip(); + + // The signature needs to follow the order [input_tys, va_list_ty, output_ty] + return inputs_and_output.map_bound(|tys| { + let (output_ty, input_tys) = tys.split_last().unwrap(); + tcx.mk_type_list_from_iter( + input_tys.iter().copied().chain([va_list_ty, *output_ty]), + ) + }); + } + + inputs_and_output + } + + DefiningTy::Const(def_id, _) => { + // For a constant body, there are no inputs, and one + // "output" (the type of the constant). + let ty = tcx.type_of(def_id).instantiate_identity().skip_norm_wip(); + ty::Binder::dummy(tcx.mk_type_list(&[ty])) + } + + DefiningTy::InlineConst(_def_id, args) => { + let ty = args.as_inline_const().ty(); + ty::Binder::dummy(tcx.mk_type_list(&[ty])) + } + + DefiningTy::GlobalAsm(def_id) => ty::Binder::dummy( + tcx.mk_type_list(&[tcx.type_of(def_id).instantiate_identity().skip_norm_wip()]), + ), + } + } + /// Returns a list of all the upvar types for this MIR. If this is /// not a closure or coroutine, there are no upvars, and hence it /// will be an empty list. The order of types in this list will @@ -581,82 +779,23 @@ impl<'tcx> UniversalRegionsBuilder<'_, 'tcx> { } } - /// Returns the "defining type" of the current MIR; - /// see `DefiningTy` for details. + /// Returns the "defining type" of the current MIR; see `DefiningTy` for details. fn defining_ty(&self) -> DefiningTy<'tcx> { - let tcx = self.infcx.tcx; - - match tcx.hir_body_owner_kind(self.mir_def) { - BodyOwnerKind::Closure | BodyOwnerKind::Fn => { - let defining_ty = tcx.type_of(self.mir_def).instantiate_identity().skip_norm_wip(); - - debug!("defining_ty (pre-replacement): {:?}", defining_ty); - - let defining_ty = self.infcx.replace_free_regions_with_nll_infer_vars( - NllRegionVariableOrigin::FreeRegion, - defining_ty, - ); - - match *defining_ty.kind() { - ty::Closure(def_id, args) => DefiningTy::Closure(def_id, args), - ty::Coroutine(def_id, args) => DefiningTy::Coroutine(def_id, args), - ty::CoroutineClosure(def_id, args) => { - DefiningTy::CoroutineClosure(def_id, args) - } - ty::FnDef(def_id, args) => { - DefiningTy::FnDef(def_id, args.no_bound_vars().unwrap()) - } - _ => span_bug!( - tcx.def_span(self.mir_def), - "expected defining type for `{:?}`: `{:?}`", - self.mir_def, - defining_ty - ), - } - } - - BodyOwnerKind::Const { .. } | BodyOwnerKind::Static(..) => { - match tcx.def_kind(self.mir_def) { - DefKind::AnonConst - if tcx.anon_const_kind(self.mir_def) - == ty::AnonConstKind::NonTypeSystemInline => - { - // This is required for `AscribeUserType` canonical query, which will call - // `type_of(inline_const_def_id)`. That `type_of` would inject erased lifetimes - // into borrowck, which is ICE #78174. - // - // As a workaround, inline consts have an additional generic param (`ty` - // below), so that `type_of(inline_const_def_id).substs(substs)` uses the - // proper type with NLL infer vars. - // - // Fetch the actual type from MIR, as `type_of` returns something useless - // like ``. - let body = tcx.mir_promoted(self.mir_def).0.borrow(); - let ty = body.local_decls[RETURN_PLACE].ty; - let typeck_root_def_id = tcx.typeck_root_def_id(self.mir_def.to_def_id()); - let parent_args = GenericArgs::identity_for_item(tcx, typeck_root_def_id); - let args = - InlineConstArgs::new(tcx, InlineConstArgsParts { parent_args, ty }) - .args; - let args = self.infcx.replace_free_regions_with_nll_infer_vars( - NllRegionVariableOrigin::FreeRegion, - args, - ); - DefiningTy::InlineConst(self.mir_def.to_def_id(), args) - } - _ => { - let identity_args = - GenericArgs::identity_for_item(tcx, self.mir_def.to_def_id()); - let args = self.infcx.replace_free_regions_with_nll_infer_vars( - NllRegionVariableOrigin::FreeRegion, - identity_args, - ); - DefiningTy::Const(self.mir_def.to_def_id(), args) - } - } + let defining_ty = DefiningTy::new(self.infcx.tcx, self.mir_def); + let f = |args| { + let fr = NllRegionVariableOrigin::FreeRegion; + self.infcx.replace_free_regions_with_nll_infer_vars(fr, args) + }; + match defining_ty { + DefiningTy::Closure(def_id, args) => DefiningTy::Closure(def_id, f(args)), + DefiningTy::Coroutine(def_id, args) => DefiningTy::Coroutine(def_id, f(args)), + DefiningTy::CoroutineClosure(def_id, args) => { + DefiningTy::CoroutineClosure(def_id, f(args)) } - - BodyOwnerKind::GlobalAsm => DefiningTy::GlobalAsm(self.mir_def.to_def_id()), + DefiningTy::FnDef(def_id, args) => DefiningTy::FnDef(def_id, f(args)), + DefiningTy::Const(def_id, args) => DefiningTy::Const(def_id, f(args)), + DefiningTy::InlineConst(def_id, args) => DefiningTy::InlineConst(def_id, f(args)), + DefiningTy::GlobalAsm(def_id) => DefiningTy::GlobalAsm(def_id), } } @@ -694,163 +833,13 @@ impl<'tcx> UniversalRegionsBuilder<'_, 'tcx> { defining_ty: DefiningTy<'tcx>, ) -> ty::Binder<'tcx, &'tcx ty::List>> { let tcx = self.infcx.tcx; + let inputs_and_output = defining_ty.inputs_and_output(tcx, || { + self.infcx.next_nll_region_var(NllRegionVariableOrigin::FreeRegion, || { + RegionCtxt::Free(sym::c_dash_variadic) + }) + }); - let inputs_and_output = match defining_ty { - DefiningTy::Closure(def_id, args) => { - assert_eq!(self.mir_def.to_def_id(), def_id); - let closure_sig = args.as_closure().sig(); - let inputs_and_output = closure_sig.inputs_and_output(); - let bound_vars = tcx.mk_bound_variable_kinds_from_iter( - inputs_and_output.bound_vars().iter().chain(iter::once( - ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv), - )), - ); - let br = ty::BoundRegion { - var: ty::BoundVar::from_usize(bound_vars.len() - 1), - kind: ty::BoundRegionKind::ClosureEnv, - }; - let env_region = ty::Region::new_bound(tcx, ty::INNERMOST, br); - let closure_ty = tcx.closure_env_ty( - Ty::new_closure(tcx, def_id, args), - args.as_closure().kind(), - env_region, - ); - - // The "inputs" of the closure in the - // signature appear as a tuple. The MIR side - // flattens this tuple. - let (&output, tuplized_inputs) = - inputs_and_output.skip_binder().split_last().unwrap(); - assert_eq!(tuplized_inputs.len(), 1, "multiple closure inputs"); - let &ty::Tuple(inputs) = tuplized_inputs[0].kind() else { - bug!("closure inputs not a tuple: {:?}", tuplized_inputs[0]); - }; - - ty::Binder::bind_with_vars( - tcx.mk_type_list_from_iter( - iter::once(closure_ty).chain(inputs).chain(iter::once(output)), - ), - bound_vars, - ) - } - - DefiningTy::Coroutine(def_id, args) => { - assert_eq!(self.mir_def.to_def_id(), def_id); - let resume_ty = args.as_coroutine().resume_ty(); - let output = args.as_coroutine().return_ty(); - let coroutine_ty = Ty::new_coroutine(tcx, def_id, args); - let inputs_and_output = - self.infcx.tcx.mk_type_list(&[coroutine_ty, resume_ty, output]); - ty::Binder::dummy(inputs_and_output) - } - - // Construct the signature of the CoroutineClosure for the purposes of borrowck. - // This is pretty straightforward -- we: - // 1. first grab the `coroutine_closure_sig`, - // 2. compute the self type (`&`/`&mut`/no borrow), - // 3. flatten the tupled_input_tys, - // 4. construct the correct generator type to return with - // `CoroutineClosureSignature::to_coroutine_given_kind_and_upvars`. - // Then we wrap it all up into a list of inputs and output. - DefiningTy::CoroutineClosure(def_id, args) => { - assert_eq!(self.mir_def.to_def_id(), def_id); - let closure_sig = args.as_coroutine_closure().coroutine_closure_sig(); - let bound_vars = - tcx.mk_bound_variable_kinds_from_iter(closure_sig.bound_vars().iter().chain( - iter::once(ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv)), - )); - let br = ty::BoundRegion { - var: ty::BoundVar::from_usize(bound_vars.len() - 1), - kind: ty::BoundRegionKind::ClosureEnv, - }; - let env_region = ty::Region::new_bound(tcx, ty::INNERMOST, br); - let closure_kind = args.as_coroutine_closure().kind(); - - let closure_ty = tcx.closure_env_ty( - Ty::new_coroutine_closure(tcx, def_id, args), - closure_kind, - env_region, - ); - - let inputs = closure_sig.skip_binder().tupled_inputs_ty.tuple_fields(); - let output = closure_sig.skip_binder().to_coroutine_given_kind_and_upvars( - tcx, - args.as_coroutine_closure().parent_args(), - tcx.coroutine_for_closure(def_id), - closure_kind, - env_region, - args.as_coroutine_closure().tupled_upvars_ty(), - args.as_coroutine_closure().coroutine_captures_by_ref_ty(), - ); - - ty::Binder::bind_with_vars( - tcx.mk_type_list_from_iter( - iter::once(closure_ty).chain(inputs).chain(iter::once(output)), - ), - bound_vars, - ) - } - - DefiningTy::FnDef(def_id, _) => { - let sig = tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); - let sig = indices.fold_to_region_vids(tcx, sig); - let inputs_and_output = sig.inputs_and_output(); - - // C-variadic fns also have a `VaList` input that's not listed in the signature - // (as it's created inside the body itself, not passed in from outside). - if self.infcx.tcx.fn_sig(def_id).skip_binder().c_variadic() { - let va_list_did = self - .infcx - .tcx - .require_lang_item(LangItem::VaList, self.infcx.tcx.def_span(self.mir_def)); - - let reg_vid = self - .infcx - .next_nll_region_var(NllRegionVariableOrigin::FreeRegion, || { - RegionCtxt::Free(sym::c_dash_variadic) - }) - .as_var(); - - let region = ty::Region::new_var(self.infcx.tcx, reg_vid); - let va_list_ty = self - .infcx - .tcx - .type_of(va_list_did) - .instantiate(self.infcx.tcx, &[region.into()]) - .skip_norm_wip(); - - // The signature needs to follow the order [input_tys, va_list_ty, output_ty] - return inputs_and_output.map_bound(|tys| { - let (output_ty, input_tys) = tys.split_last().unwrap(); - tcx.mk_type_list_from_iter( - input_tys.iter().copied().chain([va_list_ty, *output_ty]), - ) - }); - } - - inputs_and_output - } - - DefiningTy::Const(def_id, _) => { - // For a constant body, there are no inputs, and one - // "output" (the type of the constant). - assert_eq!(self.mir_def.to_def_id(), def_id); - let ty = tcx.type_of(self.mir_def).instantiate_identity().skip_norm_wip(); - - let ty = indices.fold_to_region_vids(tcx, ty); - ty::Binder::dummy(tcx.mk_type_list(&[ty])) - } - - DefiningTy::InlineConst(def_id, args) => { - assert_eq!(self.mir_def.to_def_id(), def_id); - let ty = args.as_inline_const().ty(); - ty::Binder::dummy(tcx.mk_type_list(&[ty])) - } - - DefiningTy::GlobalAsm(def_id) => ty::Binder::dummy( - tcx.mk_type_list(&[tcx.type_of(def_id).instantiate_identity().skip_norm_wip()]), - ), - }; + let inputs_and_output = indices.fold_to_region_vids(tcx, inputs_and_output); // FIXME(#129952): We probably want a more principled approach here. if let Err(e) = inputs_and_output.error_reported() { From df4b2ec75ec3625353d87178f4aff717fcc7a470 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 5 Aug 2026 08:35:42 +0200 Subject: [PATCH 014/100] Prepare for merging from rust-lang/rust This updates the rust-version file to 7218ebe93668f51a94a572b690c433dfdbdc2c3d. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index 2f35beeceda8c..f29c624515673 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -73dc9167f1cd099e525c9ade2e068d1907b78564 +7218ebe93668f51a94a572b690c433dfdbdc2c3d From e6a0fa2592f1be3df9091450e0069385afdb0333 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 5 Aug 2026 08:36:47 +0200 Subject: [PATCH 015/100] fmt --- src/tools/miri/src/diagnostics.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index d4fe89d4f0258..7e8c49bf9fba0 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -372,10 +372,7 @@ pub fn report_result<'tcx>( .. }) => { ecx.handle_ice(); // print interpreter backtrace (this is outside the eval `catch_unwind`) - bug!( - "This validation error should be impossible in Miri: {}", - res.to_string() - ); + bug!("This validation error should be impossible in Miri: {}", res.to_string()); } UndefinedBehavior(_) => "Undefined Behavior", ResourceExhaustion(_) => "resource exhaustion", From efc7c7497b30548d628f3994a48916fae5b6eb88 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 5 Aug 2026 08:39:45 +0200 Subject: [PATCH 016/100] remove readdir_r now that we no longer need it --- src/tools/miri/src/shims/unix/fs.rs | 82 ------------------- .../src/shims/unix/macos/foreign_items.rs | 6 -- src/tools/miri/tests/pass-dep/libc/libc-fs.rs | 48 ----------- 3 files changed, 136 deletions(-) diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs index c72d85bb87341..8594e7ea35e4e 100644 --- a/src/tools/miri/src/shims/unix/fs.rs +++ b/src/tools/miri/src/shims/unix/fs.rs @@ -1255,88 +1255,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(()) } - fn macos_readdir_r( - &mut self, - dirp_op: &OpTy<'tcx>, - entry_op: &OpTy<'tcx>, - result_op: &OpTy<'tcx>, - ) -> InterpResult<'tcx, Scalar> { - let this = self.eval_context_mut(); - - this.assert_target_os(Os::MacOs, "readdir_r"); - - let dirp = this.read_target_usize(dirp_op)?; - let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?; - - // Reject if isolation is enabled. - if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op { - this.reject_in_isolation("`readdir_r`", reject_with)?; - // Return error code, do *not* set `errno`. - return interp_ok(this.eval_libc("EBADF")); - } - - let open_dir = this.machine.dirs.streams.get_mut(&dirp).ok_or_else(|| { - err_unsup_format!("the DIR pointer passed to readdir_r did not come from opendir") - })?; - interp_ok(match open_dir.next_host_entry() { - Some(Ok(dir_entry)) => { - let dir_entry = this.dir_entry_fields(dir_entry)?; - // Write into entry, write pointer to result, return 0 on success. - // The name is written with write_os_str_to_c_str, while the rest of the - // dirent struct is written using write_int_fields. - - // For reference, on macOS this looks like: - // pub struct dirent { - // pub d_ino: u64, - // pub d_seekoff: u64, - // pub d_reclen: u16, - // pub d_namlen: u16, - // pub d_type: u8, - // pub d_name: [c_char; 1024], - // } - - let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?; - - // Write the name. - let name_place = this.project_field_named(&entry_place, "d_name")?; - let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str( - &dir_entry.name, - name_place.ptr(), - name_place.layout.size.bytes(), - )?; - if !name_fits { - throw_unsup_format!( - "a directory entry had a name too large to fit in libc::dirent" - ); - } - - // Write the other fields. - this.write_int_fields_named( - &[ - ("d_reclen", entry_place.layout.size.bytes().into()), - ("d_namlen", file_name_buf_len.strict_sub(1).into()), - ("d_type", dir_entry.d_type.into()), - ("d_ino", dir_entry.ino.into()), - ("d_seekoff", 0), - ], - &entry_place, - )?; - this.write_scalar(this.read_scalar(entry_op)?, &result_place)?; - - Scalar::from_i32(0) - } - None => { - // end of stream: return 0, assign *result=NULL - this.write_null(&result_place)?; - Scalar::from_i32(0) - } - Some(Err(e)) => { - // return positive error number on error (do *not* set last error) - this.host_error_to_errnum(e)? - } - }) - } - fn closedir(&mut self, dirp_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs index 3289d569173f4..9254031a8a4d1 100644 --- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs +++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs @@ -71,12 +71,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let [dirp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; this.readdir(dirp, dest)?; } - "readdir_r" | "readdir_r$INODE64" => { - let [dirp, entry, result] = - this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.macos_readdir_r(dirp, entry, result)?; - this.write_scalar(result, dest)?; - } "realpath$DARWIN_EXTSN" => { let [path, resolved_path] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs index 29f32df2dd0eb..647012eb8f7cf 100644 --- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs +++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs @@ -60,8 +60,6 @@ fn main() { test_ioctl(); test_opendir_closedir(); test_readdir(); - #[cfg(target_os = "macos")] - test_readdir_r(); #[cfg(target_os = "linux")] test_statx_on_file_path(); #[cfg(target_os = "linux")] @@ -1023,52 +1021,6 @@ fn test_readdir() { remove_dir(&dir_path).unwrap(); } -// We only support `readdir_r` on macOS. -// (It is deprecated so we don't want to add more support.) -#[cfg(target_os = "macos")] -fn test_readdir_r() { - use std::fs::{create_dir, remove_dir, write}; - use std::mem::MaybeUninit; - - let dir_path = utils::prepare_dir("miri_test_libc_readdir_r"); - create_dir(&dir_path).ok(); - - // Create test files - let file1 = dir_path.join("file1.txt"); - let file2 = dir_path.join("file2.txt"); - write(&file1, b"content1").unwrap(); - write(&file2, b"content2").unwrap(); - - let c_path = CString::new(dir_path.as_os_str().as_bytes()).unwrap(); - - unsafe { - let dirp = libc::opendir(c_path.as_ptr()); - assert!(!dirp.is_null()); - let mut entries = Vec::new(); - loop { - let mut entry: MaybeUninit = MaybeUninit::uninit(); - let mut result: *mut libc::dirent = std::ptr::null_mut(); - let ret = libc::readdir_r(dirp, entry.as_mut_ptr(), &mut result); - assert_eq!(ret, 0); - let entry_ptr = result; - if entry_ptr.is_null() { - break; - } - let name_ptr = std::ptr::addr_of!((*entry_ptr).d_name) as *const libc::c_char; - let name = CStr::from_ptr(name_ptr); - let name_str = name.to_string_lossy(); - entries.push(name_str.into_owned()); - } - assert_eq!(libc::closedir(dirp), 0); - entries.sort(); - assert_eq!(&entries, &[".", "..", "file1.txt", "file2.txt"]); - } - - remove_file(&file1).unwrap(); - remove_file(&file2).unwrap(); - remove_dir(&dir_path).unwrap(); -} - /// Check that all common fields of a `stat` struct are initialized. pub fn check_stat_fields(stat: &libc::stat) { let _st_nlink = stat.st_nlink; From ec46f102ea5df40b7c8a20312613a694041e3321 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 5 Aug 2026 08:41:53 +0200 Subject: [PATCH 017/100] fix priroda build --- src/tools/miri/priroda/src/main.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/tools/miri/priroda/src/main.rs b/src/tools/miri/priroda/src/main.rs index bc0dacc589a79..4978b205b8a99 100644 --- a/src/tools/miri/priroda/src/main.rs +++ b/src/tools/miri/priroda/src/main.rs @@ -22,7 +22,7 @@ use std::ops::Range; use std::path::PathBuf; use miri::Immediate::Uninit; -use miri::{interpret, *}; +use miri::*; use rustc_abi::{FIRST_VARIANT, FieldIdx, Size}; use rustc_driver::Compilation; use rustc_hir::attrs::CrateType; @@ -688,7 +688,7 @@ impl<'tcx> PrirodaContext<'tcx> { Either::Left(mplace) => match self.render_mplace_bytes(&mplace).report_err() { Ok(bytes) => bytes, - Err(err) => format!("", interpret::format_interp_error(err)), + Err(err) => format!("", err.to_string()), }, } } @@ -857,9 +857,7 @@ impl<'tcx> PrirodaContext<'tcx> { .ecx .eval_place_to_op(*place, None) .map(|op| self.render_source_shaped_op(op)) - .unwrap_or_else(|err| { - format!("", interpret::format_interp_error(err)) - }); + .unwrap_or_else(|err| format!("", err.to_string())); local_descs.push(LocalDesc { source_name: Some(var_debug_info.name), From 50ad30b6de63b57773d6de5a5588b80a2c9fb9f6 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Wed, 29 Jul 2026 22:49:32 +0300 Subject: [PATCH 018/100] [Priroda] Extract CLI command result rendering Move command-result printing into a helper and keep CLI loop control at the call site. --- src/tools/miri/priroda/src/main.rs | 154 +++++++++++++++-------------- 1 file changed, 81 insertions(+), 73 deletions(-) diff --git a/src/tools/miri/priroda/src/main.rs b/src/tools/miri/priroda/src/main.rs index 4978b205b8a99..acff1b8666419 100644 --- a/src/tools/miri/priroda/src/main.rs +++ b/src/tools/miri/priroda/src/main.rs @@ -920,78 +920,10 @@ impl Cli { } if let Some(command) = self.parse_command(&input) { - match session.run_command(command)? { - CommandResult::ExecutionStopped(result) => { - if matches!(result, StepResult::Breakpoint) { - println!("Hit breakpoint"); - } - self.print_location(session); - } - CommandResult::BreakpointResult(res) => - match res { - BreakpointSetResult::Added(path, line) => - println!("breakpoint added: {}:{}", path.display(), line), - - BreakpointSetResult::Duplicate => println!("Duplicate breakpoint"), - }, - CommandResult::Locals(locals_desc) => - if locals_desc.is_empty() { - println!("no locals"); - } else { - for local_desc in &locals_desc { - let source_projection = local_desc - .source_projection - .as_ref() - .map(|fields| { - fields - .iter() - .map(|field| field.to_string()) - .collect::() - }) - .unwrap_or_default(); - - let name = local_desc - .source_name - .map_or_else(|| "".to_string(), |name| name.to_string()); - - let display_name = format!("{name}{source_projection}"); - - let local_id = local_desc.local.map_or_else( - || "".to_string(), - |local_idx| format!("_{}", local_idx.index()), - ); - - let storage_projection = local_desc - .storage_projection - .iter() - .map(StorageProj::render) - .collect::(); - - let display_local_id = format!("{local_id}{storage_projection}"); - println!( - "Name: {}, Id: {}, Ty: {}, Value: {}", - display_name, display_local_id, local_desc.ty, local_desc.value - ); - } - }, - CommandResult::SingleLocal(local_desc) => - match local_desc { - Some(local_desc) => { - println!( - "Id: _{}, Ty: {}, Value: {}", - local_desc.local.unwrap().index(), - local_desc.ty, - local_desc.value - ); - } - None => println!("no local for this id"), - }, - CommandResult::Memory(memory) => println!("{memory}"), - CommandResult::TerminateSession => { - println!("quitting"); - return interp_ok(()); - } - } + let command_res = session.run_command(command)?; + if !Self::print_command_result(command_res, session)? { + return interp_ok(()); + }; } else { println!("no command"); } @@ -1000,6 +932,82 @@ impl Cli { } } + fn print_command_result<'tcx>( + command_res: CommandResult, + session: &PrirodaContext<'tcx>, + ) -> InterpResult<'tcx, bool> { + match command_res { + CommandResult::ExecutionStopped(result) => { + if matches!(result, StepResult::Breakpoint) { + println!("Hit breakpoint"); + } + Self::print_location(session); + } + CommandResult::BreakpointResult(res) => + match res { + BreakpointSetResult::Added(path, line) => + println!("breakpoint added: {}:{}", path.display(), line), + + BreakpointSetResult::Duplicate => println!("Duplicate breakpoint"), + }, + CommandResult::Locals(locals_desc) => + if locals_desc.is_empty() { + println!("no locals"); + } else { + for local_desc in &locals_desc { + let source_projection = local_desc + .source_projection + .as_ref() + .map(|fields| { + fields.iter().map(|field| field.to_string()).collect::() + }) + .unwrap_or_default(); + + let name = local_desc + .source_name + .map_or_else(|| "".to_string(), |name| name.to_string()); + + let display_name = format!("{name}{source_projection}"); + + let local_id = local_desc.local.map_or_else( + || "".to_string(), + |local_idx| format!("_{}", local_idx.index()), + ); + + let storage_projection = local_desc + .storage_projection + .iter() + .map(StorageProj::render) + .collect::(); + + let display_local_id = format!("{local_id}{storage_projection}"); + println!( + "Name: {}, Id: {}, Ty: {}, Value: {}", + display_name, display_local_id, local_desc.ty, local_desc.value + ); + } + }, + CommandResult::SingleLocal(local_desc) => + match local_desc { + Some(local_desc) => { + println!( + "Id: _{}, Ty: {}, Value: {}", + local_desc.local.unwrap().index(), + local_desc.ty, + local_desc.value + ); + } + None => println!("no local for this id"), + }, + CommandResult::Memory(memory) => println!("{memory}"), + CommandResult::TerminateSession => { + println!("quitting"); + return interp_ok(false); + } + } + interp_ok(true) + } + fn parse_command(&self, input: &str) -> Option { // TODO: look at the Spanned crate for how to easily produce errors in // rustc's style while manually parsing text input. @@ -1024,7 +1032,7 @@ impl Cli { } } - fn print_location(&self, session: &PrirodaContext) { + fn print_location(session: &PrirodaContext) { match &session.current_location { Some(location) => if let Some(path) = session.local_path(location) { From 049c9da058b891a3a054685e47e6be7caf03bc1d Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Wed, 29 Jul 2026 23:22:40 +0300 Subject: [PATCH 019/100] [Priroda] Add initial DAP frontend selection Consume Priroda's --dap flag before handing arguments to rustc_driver::run_compiler, then dispatch the freshly-created PrirodaContext to either the existing CLI loop or a new DAP loop stub. --- src/tools/miri/priroda/src/main.rs | 65 ++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/src/tools/miri/priroda/src/main.rs b/src/tools/miri/priroda/src/main.rs index acff1b8666419..bb2327c8338e0 100644 --- a/src/tools/miri/priroda/src/main.rs +++ b/src/tools/miri/priroda/src/main.rs @@ -46,6 +46,7 @@ fn main() { rustc_driver::init_rustc_env_logger(&early_dcx); let mut args: Vec = std::env::args().collect(); + let frontend = Frontend::parse_from_args(&mut args); args.splice(1..1, miri::MIRI_DEFAULT_ARGS.iter().map(ToString::to_string)); @@ -55,15 +56,48 @@ fn main() { args.push(find_sysroot()); } // FIXME: handle the same `-Z` flags that Miri accepts. - rustc_driver::run_compiler(&args, &mut PrirodaCompilerCalls::new()); + rustc_driver::run_compiler(&args, &mut PrirodaCompilerCalls::new(frontend)); } -struct PrirodaCompilerCalls; +/// Frontend selected by Priroda-specific CLI flags. +#[derive(Clone, Copy)] +enum Frontend { + Cli, + Dap, +} + +impl Frontend { + /// Remove Priroda-only flags before forwarding the remaining arguments to rustc. + fn parse_from_args(args: &mut Vec) -> Self { + let mut frontend = Frontend::Cli; + let mut rustc_args = Vec::with_capacity(args.len()); + let mut parsing_priroda_args = true; + + for (idx, arg) in args.drain(..).enumerate() { + if idx != 0 && parsing_priroda_args && arg == "--dap" { + frontend = Frontend::Dap; + continue; + } + + if arg == "--" { + parsing_priroda_args = false; + } + + rustc_args.push(arg); + } + + *args = rustc_args; + frontend + } +} + +struct PrirodaCompilerCalls { + frontend: Frontend, +} impl PrirodaCompilerCalls { - // FIXME: remove this constructor if PrirodaCompilerCalls remains a unit struct. - fn new() -> Self { - Self + fn new(frontend: Frontend) -> Self { + Self { frontend } } } @@ -80,8 +114,16 @@ impl rustc_driver::Callbacks for PrirodaCompilerCalls { let ecx = create_ecx(tcx); let mut session = PrirodaContext::new(ecx); - let cli = Cli {}; - let result = cli.run_cli_loop(&mut session); + let result = match self.frontend { + Frontend::Cli => { + let cli = Cli {}; + cli.run_cli_loop(&mut session) + } + Frontend::Dap => { + let dap = Dap {}; + dap.run_dap_loop(&mut session) + } + }; match result.report_err() { Ok(()) => {} @@ -1075,3 +1117,12 @@ impl Cli { Some(DebuggerCommand::Follow(alloc_id, offset)) } } + +struct Dap; + +impl Dap { + pub fn run_dap_loop<'tcx>(&self, _session: &mut PrirodaContext<'tcx>) -> InterpResult<'tcx> { + // FIXME: implement DAP framing and request dispatch on top of PrirodaContext. + interp_ok(()) + } +} From 7b174e3b1adb76adada454c28033bd9988e538f6 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Wed, 29 Jul 2026 23:22:40 +0300 Subject: [PATCH 020/100] [Priroda] Add DAP UI fixtures --- src/tools/miri/priroda/tests/ui/dap_initialize.rs | 3 +++ src/tools/miri/priroda/tests/ui/dap_initialize.stdin | 3 +++ src/tools/miri/priroda/tests/ui/dap_initialize.stdout | 0 .../miri/priroda/tests/ui/dap_rejects_non_initialize_first.rs | 3 +++ .../priroda/tests/ui/dap_rejects_non_initialize_first.stdin | 3 +++ .../priroda/tests/ui/dap_rejects_non_initialize_first.stdout | 0 6 files changed, 12 insertions(+) create mode 100644 src/tools/miri/priroda/tests/ui/dap_initialize.rs create mode 100644 src/tools/miri/priroda/tests/ui/dap_initialize.stdin create mode 100644 src/tools/miri/priroda/tests/ui/dap_initialize.stdout create mode 100644 src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.rs create mode 100644 src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdin create mode 100644 src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize.rs b/src/tools/miri/priroda/tests/ui/dap_initialize.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_initialize.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize.stdin b/src/tools/miri/priroda/tests/ui/dap_initialize.stdin new file mode 100644 index 0000000000000..873743fad394b --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_initialize.stdin @@ -0,0 +1,3 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize.stdout b/src/tools/miri/priroda/tests/ui/dap_initialize.stdout new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.rs b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdin b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdin new file mode 100644 index 0000000000000..6b8fb8e08484a --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdin @@ -0,0 +1,3 @@ +Content-Length: 70 + +{"seq":2,"type":"request","command":"next","arguments":{"threadId":1}} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout new file mode 100644 index 0000000000000..e69de29bb2d1d From 1544ae76ddb9cecfebf0bab3ddba33e85bd05a4b Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Wed, 29 Jul 2026 23:38:08 +0300 Subject: [PATCH 021/100] [Priroda] Split debugger frontends into modules --- src/tools/miri/priroda/src/debugger.rs | 823 ++++++++++++++++ src/tools/miri/priroda/src/frontend/cli.rs | 176 ++++ src/tools/miri/priroda/src/frontend/dap.rs | 17 + src/tools/miri/priroda/src/frontend/mod.rs | 5 + src/tools/miri/priroda/src/main.rs | 1001 +------------------- 5 files changed, 1027 insertions(+), 995 deletions(-) create mode 100644 src/tools/miri/priroda/src/debugger.rs create mode 100644 src/tools/miri/priroda/src/frontend/cli.rs create mode 100644 src/tools/miri/priroda/src/frontend/dap.rs create mode 100644 src/tools/miri/priroda/src/frontend/mod.rs diff --git a/src/tools/miri/priroda/src/debugger.rs b/src/tools/miri/priroda/src/debugger.rs new file mode 100644 index 0000000000000..aa4cb45b85aba --- /dev/null +++ b/src/tools/miri/priroda/src/debugger.rs @@ -0,0 +1,823 @@ +use std::collections::{HashMap, HashSet}; +use std::ops::Range; +use std::path::PathBuf; + +use miri::Immediate::Uninit; +use miri::{interpret, *}; +use rustc_abi::{FIRST_VARIANT, FieldIdx, Size}; +use rustc_hir::def::CtorKind; +use rustc_middle::mir::interpret::AllocId; +use rustc_middle::mir::{self, Local, ProjectionElem, VarDebugInfoContents, VarDebugInfoFragment}; +use rustc_middle::ty::{self, TyKind}; +use rustc_span::source_map::SourceMap; +use rustc_span::{Span, Symbol}; + +/// Structured source information for frontends. +pub(super) struct SourceLocation { + // storing `span` to use it lazily to compute path. + pub(super) span: Span, + pub(super) line: usize, +} + +impl SourceLocation { + fn local_path(&self, source_map: &SourceMap) -> Option { + let loc = source_map.lookup_char_pos(self.span.lo()); + loc.file.name.clone().into_local_path().map(normalize_path) + } +} + +/// Source-level breakpoints indexed by normalized path, then line. +type BreakpointTable = HashMap>; + +/// Owns one interpreter session and its debugger state. +/// +/// Frontend rendering should eventually live outside this type. +pub(super) struct PrirodaContext<'tcx> { + pub(super) ecx: MiriInterpCx<'tcx>, + breakpoints: BreakpointTable, + pub(super) current_location: Option, + last_location: Option, +} + +pub(super) enum StorageProj { + Field(usize), + Deref, + Downcast(Symbol), + Variant(usize), + Unsupported(String), +} + +impl StorageProj { + pub(super) fn render(&self) -> String { + match self { + StorageProj::Field(field_idx) => format!(".{field_idx}"), + StorageProj::Deref => ".*".to_string(), + StorageProj::Downcast(name) => format!(" as {name}"), + StorageProj::Variant(variant_idx) => format!(" as variant#{variant_idx}"), + StorageProj::Unsupported(unsop) => format!("."), + } + } +} + +pub(super) struct LocalDesc { + /// Source variable name from `VarDebugInfo`, if this row has one. + pub(super) source_name: Option, + + /// Source-side projection from `VarDebugInfo::composite`, e.g. `.field` in source fragment `x.field`. + pub(super) source_projection: Option>, + + /// MIR storage local that backs this description, if any. + pub(super) local: Option, + + /// rendered/debug MIR place projection for now + pub(super) storage_projection: Vec, + + /// Display-rendered type for this description. + pub(super) ty: String, + + /// Run-time state for now; will be expanded later + pub(super) value: String, +} + +impl LocalDesc { + pub(super) fn source_projection_str(&self) -> String { + self.source_projection + .as_ref() + .map(|fields| fields.iter().map(|field| field.to_string()).collect::()) + .unwrap_or_default() + } + + pub(super) fn storage_projection_str(&self) -> String { + self.storage_projection.iter().map(StorageProj::render).collect::() + } +} + +/// Controls when execution returns to the frontend. +enum ResumeMode { + /// Stop at the next visible MIR instruction. + MirInstruction, + /// Stop at the next source line + /// + /// Take `Option` because some cases current state has no mapped to source code location + SourceLine(Option<(PathBuf, usize)>), + /// Continue until reaching a breakpoint. + Continue, +} + +/// Describes whether the current MIR instruction should be shown to the user. +enum InstructionVisibility { + NoInstruction, + Hidden, + Visible, +} + +/// Describes why execution stopped and returned control to the frontend. +pub(super) enum StepResult { + Step, + Breakpoint, +} + +fn normalize_path(path: PathBuf) -> PathBuf { + path.canonicalize().unwrap_or(path) +} + +impl<'tcx> PrirodaContext<'tcx> { + pub(super) fn new(ecx: MiriInterpCx<'tcx>) -> Self { + Self { ecx, breakpoints: HashMap::new(), current_location: None, last_location: None } + } + + pub(super) fn local_path(&self, location: &SourceLocation) -> Option { + let source_map = self.ecx.tcx.sess.source_map(); + location.local_path(source_map) + } + + fn current_source_position(&self) -> Option<(PathBuf, usize)> { + let location = self.current_location.as_ref()?; + Some((self.local_path(location)?, location.line)) + } + + // Used to treat `continue` like a source-level step for breakpoint checks: + // several MIR locations can point at one source line, but they should only + // report that source breakpoint once. + fn last_source_position(&self) -> Option<(PathBuf, usize)> { + let location = self.last_location.as_ref()?; + Some((self.local_path(location)?, location.line)) + } + + /// Step to the next visible MIR instruction. + fn stepi(&mut self) -> InterpResult<'tcx, StepResult> { + self.resume(ResumeMode::MirInstruction) + } + fn step(&mut self) -> InterpResult<'tcx, StepResult> { + self.resume(ResumeMode::SourceLine(self.current_source_position())) + } + + /// Continue execution until reaching a breakpoint or propagating termination. + fn continue_execution(&mut self) -> InterpResult<'tcx, StepResult> { + self.resume(ResumeMode::Continue) + } + + fn set_breakpoint(&mut self, path: PathBuf, line: usize) -> BreakpointSetResult { + // FIXME: validate breakpoints here so every frontend gets the same behavior. + // Reject empty paths, missing files, directories, and line 0. Decide whether + // out-of-range lines should be rejected or kept as pending breakpoints. + // Report duplicate registrations separately. + + let path = normalize_path(path); + match self.breakpoints.entry(path.clone()).or_default().insert(line) { + true => BreakpointSetResult::Added(path, line), + false => BreakpointSetResult::Duplicate, + } + } + + /// Advance execution until the selected resume mode reaches a stopping point. + fn resume(&mut self, mode: ResumeMode) -> InterpResult<'tcx, StepResult> { + loop { + self.advance()?; + + // An explicit breakpoint should stop execution even when the current + // MIR instruction would normally be hidden during manual stepping. + if self.is_at_breakpoint() { + return interp_ok(StepResult::Breakpoint); + } + + match mode { + ResumeMode::MirInstruction + if matches!( + self.current_instruction_visibility(), + InstructionVisibility::Visible + ) => + { + return interp_ok(StepResult::Step); + } + + ResumeMode::SourceLine(ref prev_location) => { + match (prev_location, &self.current_location) { + // We started from an unmapped source location. Stop at the first mapped source location we can show to the user. + (None, Some(_)) => return interp_ok(StepResult::Step), + + (Some((prev_path, prev_line)), Some(current_location)) => { + if let Some(current_path) = self.local_path(current_location) { + // A source step stops when the visible source position changes to a different file or line. + if *prev_path != current_path || *prev_line != current_location.line + { + return interp_ok(StepResult::Step); + } + } + } + + _ => {} + } + } + + ResumeMode::MirInstruction | ResumeMode::Continue => {} + } + } + } + + /// Advance Miri by one interpreter-loop transition. + fn advance(&mut self) -> InterpResult<'tcx> { + // FIXME: use a Miri-owned scheduler-aware debugger step API before + // claiming support for multi-threaded interpreted programs. + + // State inspection should happen only after a successful step. + self.ecx.step_current_thread()?; + self.last_location = self.current_location.take(); + self.current_location = self.resolve_current_location(); + interp_ok(()) + } + + fn current_instruction_visibility(&self) -> InstructionVisibility { + // If the active thread has no stack frame, there is no MIR instruction to show. + let Some(frame) = self.ecx.active_thread_stack().last() else { + return InstructionVisibility::NoInstruction; + }; + + // `Right(span)` means the frame has source context but no precise MIR program-counter location. + let Either::Left(location) = frame.current_loc() else { + return InstructionVisibility::NoInstruction; + }; + + let basic_block = &frame.body().basic_blocks[location.block]; + + // `statement_index == statements.len()` points at the block terminator. + // Terminators affect control flow, so they are always visible. + let Some(statement) = basic_block.statements.get(location.statement_index) else { + return InstructionVisibility::Visible; + }; + + // Hide bookkeeping-only MIR statements during manual stepping. + match statement.kind { + mir::StatementKind::StorageLive(_) + | mir::StatementKind::StorageDead(_) + | mir::StatementKind::Nop => InstructionVisibility::Hidden, + _ => InstructionVisibility::Visible, + } + } + + fn is_at_breakpoint(&self) -> bool { + let Some(bp) = self.current_breakpoint() else { + return false; + }; + + // If the previous interpreter step had the same source position, this + // is another MIR location for the breakpoint we just reported. + self.last_source_position().as_ref() != Some(&bp) + } + + fn current_breakpoint(&self) -> Option<(PathBuf, usize)> { + let (path, line) = self.current_source_position()?; + let lines = self.breakpoints.get(&path)?; + + if lines.contains(&line) { Some((path, line)) } else { None } + } + + fn resolve_current_location(&self) -> Option { + // FIXME: resolve macro-backed lines such as `println!` and `assert_eq!` + // through `span.source_callsite()` before matching breakpoints. + let span = self.ecx.machine.current_user_relevant_span(); + if span.is_dummy() { + return None; + } + + let source_map = self.ecx.tcx.sess.source_map(); + let loc = source_map.lookup_char_pos(span.lo()); + + Some(SourceLocation { span, line: loc.line }) + } + + pub(super) fn run_command( + &mut self, + command: DebuggerCommand, + ) -> InterpResult<'tcx, CommandResult> { + match command { + DebuggerCommand::StepI => self.stepi().map(CommandResult::ExecutionStopped), + DebuggerCommand::Step => self.step().map(CommandResult::ExecutionStopped), + DebuggerCommand::Continue => + self.continue_execution().map(CommandResult::ExecutionStopped), + DebuggerCommand::Breakpoint(path, line) => + interp_ok(CommandResult::BreakpointResult(self.set_breakpoint(path, line))), + DebuggerCommand::ListLocals => interp_ok(CommandResult::Locals(self.list_locals())), + DebuggerCommand::Print(local) => + interp_ok(CommandResult::SingleLocal(self.get_local(local))), + DebuggerCommand::Follow(alloc_id, offset) => + self.follow_alloc(alloc_id, offset).map(CommandResult::Memory), + DebuggerCommand::TerminateSession => interp_ok(CommandResult::TerminateSession), + } + } + + fn follow_alloc(&self, alloc_id: AllocId, offset: usize) -> InterpResult<'tcx, String> { + let alloc = self.ecx.get_alloc_raw(alloc_id)?; + if offset > alloc.len() { + return Err(miri::err_unsup_format!( + "allocation offset {offset} is outside {alloc_id}" + )) + .into(); + } + + let memory = self.render_alloc_bytes(alloc_id, offset..alloc.len())?; + interp_ok(format!("Allocation {alloc_id}+{offset}: {memory}")) + } + + fn get_local(&self, local: usize) -> Option { + let frame = self.ecx.active_thread_stack().last()?; + + self.make_mir_local_desc(frame, local) + } + + /// Returns structured descriptions for locals in the innermost stack frame. + /// + /// Starts from all MIR locals, then enriches them with source names from + /// `var_debug_info` when a debug entry maps directly to a whole local. + fn list_locals(&self) -> Vec { + let Some(frame) = self.ecx.active_thread_stack().last() else { + return Vec::new(); + }; + + self.build_local_descs(frame) + } + + /// Renders the current byte range of an indirect MIR value. + /// + /// Initialized bytes are shown in hexadecimal, uninitialized bytes as `??`, + /// and complete pointer-sized provenance as pointer markers. + fn render_mplace_bytes(&self, mplace: &MPlaceTy<'tcx>) -> InterpResult<'tcx, String> { + let size = match self.ecx.size_and_align_of_val(mplace)? { + Some((size, _)) => size, + None => { + // Extern types cannot currently be executed as by-value locals, + // so this path cannot yet be covered by a Priroda UI fixture. + // FIXME: Add coverage once Priroda supports printing dereferenced places. + return interp_ok("".to_string()); + } + }; + + let size = size.bytes_usize(); + if size == 0 { + return interp_ok("[]".to_string()); + } + + let (alloc_id, offset, _) = + self.ecx.ptr_get_alloc_id(mplace.ptr(), size.try_into().unwrap())?; + let offset = offset.bytes_usize(); + let range = offset..offset.strict_add(size); + + self.render_alloc_bytes(alloc_id, range) + } + + /// Render a raw allocation range without requiring a typed memory place. + /// + /// This is also used by the future-facing `follow` command, where we have a + /// pointer target but do not yet know the target's type or size. + fn render_alloc_bytes( + &self, + alloc_id: AllocId, + range: Range, + ) -> InterpResult<'tcx, String> { + let alloc = self.ecx.get_alloc_raw(alloc_id)?; + + let mut rendered = Vec::with_capacity(range.len()); + + let ptr_size = self.ecx.tcx.data_layout.pointer_size(); + + for chunk in alloc.init_mask().range_as_init_chunks(range.into()) { + let chunk_range = chunk.range(); + let chunk_range = chunk_range.start.bytes_usize()..chunk_range.end.bytes_usize(); + + if chunk.is_init() { + let ptr_size = ptr_size.bytes_usize(); + let mut cursor = chunk_range.start; + + while cursor < chunk_range.end { + // Full pointer provenance is rendered as a pointer marker. Bytewise + // provenance fragments are intentionally left as raw bytes here: they do + // not represent a complete pointer-sized value. + if let Some(prov) = alloc.provenance().get_ptr(Size::from_bytes(cursor)) + && cursor + ptr_size <= chunk_range.end + { + let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter( + cursor..cursor + ptr_size, + ); + let offset = read_target_uint(self.ecx.tcx.data_layout.endian, bytes) + .map_err(|err| { + miri::err_unsup_format!("invalid pointer representation: {err}") + })?; + + let offset = Size::from_bytes(offset); + rendered.push(format!("{:?}", Pointer::new(Some(prov), offset))); + + cursor += ptr_size; + } else { + let byte = alloc + .inspect_with_uninit_and_ptr_outside_interpreter(cursor..cursor + 1)[0]; + + rendered.push(format!("{byte:02x}")); + cursor += 1; + } + } + } else { + rendered.extend(std::iter::repeat_n("__".to_string(), chunk_range.len())); + } + } + + interp_ok(format!("[{}]", rendered.join(" "))) + } + + /// Render an evaluated operand using Rust-source-shaped containers with raw leaves. + /// + /// The operand is produced from live interpreter state, usually via `local_to_op` + /// for a whole MIR local or `eval_place_to_op` for a projected debug-info place. + /// + /// This intentionally does not call user `Debug` / `Display`, and it does not + /// try to make every scalar leaf pretty yet. Unsupported cases and leaf values + /// fall back to `render_op`, preserving the old raw byte/provenance renderer. + /// + /// FIXME: teach the leaf renderer about simple Rust scalars (`bool`, integers, + /// chars, raw pointers/references) once the source-shaped container output is + /// stable enough to stop depending on byte dumps for every field. + /// + /// FIXME: decide how much dereferencing belongs in this renderer. References + /// currently stay as raw pointer leaves; following them may belong in the + /// existing `follow` command instead of automatic local rendering. + fn render_source_shaped_op(&self, op: OpTy<'tcx>) -> String { + self.render_source_shaped_op_inner(op, 0) + } + + /// Recursive worker for `render_source_shaped_op`. + /// + /// The depth limit keeps cyclic/reference-heavy values from making debugger + /// output explode once more container kinds are added. At the limit, the raw + /// renderer remains the ground truth. + /// + /// FIXME: replace this fixed recursion limit with a value-size/output-budget + /// policy so large acyclic values and deeply nested values degrade more + /// predictably. + fn render_source_shaped_op_inner(&self, op: OpTy<'tcx>, depth: usize) -> String { + const MAX_SOURCE_SHAPE_DEPTH: usize = 8; + + if depth >= MAX_SOURCE_SHAPE_DEPTH { + return self.render_op(op); + } + + match op.layout.ty.kind() { + // Empty enums have no active variant to format. Unions do not record + // which field is currently active, so choosing one would be misleading. + // + // FIXME: support unions only with an explicit user-selected field or + // another source of active-field information. Guessing from layout + // bytes would make debugger output look more certain than it is. + ty::Adt(def, _) if def.variants().is_empty() || def.is_union() => self.render_op(op), + + ty::Adt(def, _) => { + // Enums need their runtime discriminant and a downcasted layout + // view before fields can be projected. Structs use their sole + // variant directly. Keep the display name tied to the same choice. + let (variant_idx, down, name) = if def.is_enum() { + let variant_idx = match self.ecx.read_discriminant(&op).discard_err() { + Some(variant_idx) => variant_idx, + // FIXME: expose this as an explicit render error when + // Priroda grows structured value states. Falling back to + // bytes keeps today's UI usable but hides why the enum + // could not be source-shaped. + None => return self.render_op(op), + }; + let down = match self.ecx.project_downcast(&op, variant_idx).discard_err() { + Some(down) => down, + // FIXME: distinguish invalid/uninitialized discriminants + // from projection bugs in the rendered output once locals + // can carry structured diagnostics. + None => return self.render_op(op), + }; + let variant_def = &def.variants()[variant_idx]; + ( + variant_idx, + down, + format!("{}::{}", self.ecx.tcx.item_name(def.did()), variant_def.name), + ) + } else { + let variant_idx = FIRST_VARIANT; + let variant_def = &def.variants()[variant_idx]; + (variant_idx, op.clone(), variant_def.name.to_string()) + }; + + let variant_def = &def.variants()[variant_idx]; + + let mut fields = Vec::with_capacity(variant_def.fields.len()); + for i in 0..variant_def.fields.len() { + let field_idx = FieldIdx::from_usize(i); + // `project_field` avoids manual offset math and works for both + // immediate and memory-backed operands through `Projectable`. + let field_op = match self.ecx.project_field(&down, field_idx).discard_err() { + Some(field_op) => field_op, + // FIXME: preserve the successfully rendered fields and + // mark only this field as unavailable once the value model + // can represent partial render failures. + None => return self.render_op(op), + }; + fields.push(self.render_source_shaped_op_inner(field_op, depth + 1)); + } + + // Match Rust constructor spelling: + // - `Const`: unit structs/variants, e.g. `UnitStruct`, `Enum::Unit` + // - `Fn`: tuple structs/variants, e.g. `Pair(a, b)` or `EmptyTuple()` + // - `None`: braced structs/variants, including the empty `{}` case + match variant_def.ctor_kind() { + Some(CtorKind::Const) => name, + Some(CtorKind::Fn) => format!("{name}({})", fields.join(", ")), + None if fields.is_empty() => format!("{name} {{}}"), + None => { + let fields = variant_def + .fields + .iter() + .zip(fields) + .map(|(field_def, value)| format!("{}: {value}", field_def.name)) + .collect::>() + .join(", "); + format!("{name} {{ {fields} }}") + } + } + } + + ty::Tuple(args) => { + let mut fields = Vec::with_capacity(args.len()); + for i in 0..args.len() { + // Tuples have no field names in source, so preserve their + // source field order and render children positionally. + let field_op = + match self.ecx.project_field(&op, FieldIdx::from_usize(i)).discard_err() { + Some(field_op) => field_op, + // FIXME: render tuple fields independently so one + // projection failure does not throw away the whole + // source-shaped tuple. + None => return self.render_op(op), + }; + fields.push(self.render_source_shaped_op_inner(field_op, depth + 1)); + } + + if fields.len() == 1 { + format!("({},)", fields[0]) + } else { + format!("({})", fields.join(", ")) + } + } + + ty::Array(_, _) | ty::Slice(_) => { + // `project_array_fields` uses the dynamic length for slices. That + // avoids the classic mistake of treating slice layout as a fixed + // zero-length array. + let mut iter = match self.ecx.project_array_fields(&op).discard_err() { + Some(iter) => iter, + // FIXME: when slice metadata is invalid, show that as a slice + // length problem instead of silently falling back to raw bytes. + None => return self.render_op(op), + }; + + let mut fields = Vec::new(); + // FIXME: add an output budget/truncation policy before rendering + // very large arrays or slices in full. + loop { + match iter.next(&self.ecx).discard_err() { + Some(Some((_idx, field_op))) => + fields.push(self.render_source_shaped_op_inner(field_op, depth + 1)), + Some(None) => break, + // FIXME: keep already-rendered elements and mark the + // failed index once partial render errors are supported. + None => return self.render_op(op), + } + } + + format!("[{}]", fields.join(", ")) + } + + // FIXME: consider source-shaped special cases for strings, closures, + // generators/coroutines, trait objects, and SIMD/vector-like types. + // Until then these stay on the raw renderer path. + _ => self.render_op(op), + } + } + + /// Render an evaluated operand using the same raw representation for + /// whole locals and projected MIR places. + fn render_op(&self, op: OpTy<'tcx>) -> String { + match op.as_mplace_or_imm() { + Either::Right(imm) => format!("{imm}"), + + Either::Left(mplace) => + match self.render_mplace_bytes(&mplace).report_err() { + Ok(bytes) => bytes, + Err(err) => format!("", interpret::format_interp_error(err)), + }, + } + } + + /// Render the source-side path from composite debug info, such as `.field`. + fn render_source_projection( + fragment: Option<&VarDebugInfoFragment<'tcx>>, + ) -> Option> { + let VarDebugInfoFragment { ty, projection } = fragment?; + + // Walk the source-side projection from the original + // composite variable type. Each `Field` element stores the + // resulting field type, so resolve the field name from the + // current base type before advancing to `field_ty`. + let mut projection_ty = ty; + + Some( + projection + .iter() + .map(|elem| { + match elem { + ProjectionElem::Field(field_idx, field_ty) => { + let rendered = match projection_ty.kind() { + TyKind::Adt(adt_def, _args) if adt_def.is_struct() => { + let variant = adt_def.non_enum_variant(); + let field = &variant.fields[*field_idx]; + Symbol::intern(&format!(".{}", field.name)) + } + + TyKind::Tuple(_) => + Symbol::intern(&format!(".{}", field_idx.index())), + + _ => Symbol::intern("."), + }; + + projection_ty = field_ty; + + rendered + } + // `VarDebugInfoFragment::projection` is expected to be + // field-only. If that ever changes, keep the unexpected + // segment visible instead of silently rendering a + // misleading source path. + other => Symbol::intern(&format!(".")), + } + }) + .collect(), + ) + } + + /// Render the MIR storage-side path that backs a debug-info local. + fn render_storage_projection(projection: &[mir::PlaceElem<'tcx>]) -> Vec { + projection + .iter() + .map(|projection_elem| { + match projection_elem { + ProjectionElem::Field(field_idx, _) => StorageProj::Field(field_idx.index()), + ProjectionElem::Deref => StorageProj::Deref, + ProjectionElem::Downcast(Some(name), _) => StorageProj::Downcast(*name), + ProjectionElem::Downcast(None, variant_idx) => + StorageProj::Variant(variant_idx.index()), + other => StorageProj::Unsupported(format!("{other:?}")), + } + }) + .collect() + } + + /// Builds the baseline debugger row for one MIR local without scanning debug info. + fn make_mir_local_desc( + &self, + frame: &Frame<'tcx, Provenance, FrameExtra<'tcx>>, + local: usize, + ) -> Option { + let local = mir::Local::from_usize(local); + let local_decl = frame.body().local_decls.get(local)?; + + // Create LocalDesc for MIR local before processing debug info. + // Debug-info enrichment is layered on by build_local_descs. + let mut local_desc = LocalDesc { + source_name: None, + source_projection: None, + local: Some(local), + storage_projection: Vec::new(), + ty: local_decl.ty.to_string(), + value: "".to_string(), + }; + + match &frame.locals[local].as_mplace_or_imm() { + None => { + local_desc.value = "".to_string(); + } + Some(Either::Right(Uninit)) => local_desc.value = "".to_string(), + + Some(Either::Left(_) | Either::Right(_)) => { + let op = self + .ecx + .local_to_op(local, None) + .expect("this error can only occur in CTFE on generic code"); + local_desc.value = self.render_source_shaped_op(op); + } + }; + + Some(local_desc) + } + + fn build_local_descs( + &self, + frame: &Frame<'tcx, Provenance, FrameExtra<'tcx>>, + ) -> Vec { + let local_decls = &frame.body().local_decls; + + let mut local_descs: Vec = Vec::with_capacity(local_decls.len()); + + // Start with one baseline row for every MIR local, then layer debug info on top. + for (local_idx, _) in local_decls.iter_enumerated() { + local_descs.push(self.make_mir_local_desc(frame, local_idx.index()).unwrap()); + } + + // FIXME: Finish classifying `var_debug_info` by keeping the source path + // and MIR storage path separate: + // + // - source side: `var_debug_info.name` plus + // `var_debug_info.composite.projection` + // - storage side: `VarDebugInfoContents::Place(place).local` plus + // `place.projection` + // + // Already handled by the `place.as_local()` path below: + // - whole source variable -> whole MIR local: + // `composite = None`, `Place(_N)` with empty projection. + // - source fragment -> whole MIR local: + // `composite = Some(source_proj)`, `Place(_N)` with empty projection. + // + // Remaining cases to represent or explicitly defer: + // - whole source variable -> projected MIR storage: + // `composite = None`, `Place(_N.proj)`. + // - source fragment -> projected MIR storage: + // `composite = Some(source_proj)`, `Place(_N.storage_proj)`. + // - source variable/fragment -> constant: + // `Const(...)`, with no MIR local id. + // - optimized-out/debug-only/unsupported shapes: + // explicit deferred state, not silent discard. + // + // Final output should be produced by walking `Vec`, + // then append explicit deferred/debug-info-only rows where needed. + // Related: SROA can split a source local like `_slice: ExtraSlice` into + // field locals whose debug paths should be printed as `_slice._slice` + // and `_slice._extra`, not as two separate locals both named `_slice`. + + // Whole-place debug entries enrich the direct storage-local description. + // Projected places are evaluated from their original MIR Place and use + // the same raw renderer as ordinary locals. + for var_debug_info in &frame.body().var_debug_info { + if let VarDebugInfoContents::Place(place) = &var_debug_info.value { + if let Some(local_idx) = place.as_local() + && local_descs[local_idx.index()].source_name.is_none() + { + let local_idx = local_idx.index(); + local_descs[local_idx].source_projection = + Self::render_source_projection(var_debug_info.composite.as_deref()); + local_descs[local_idx].source_name = Some(var_debug_info.name); + } else if !place.projection.is_empty() { + let storage_projection = Self::render_storage_projection(place.projection); + let source_projection = + Self::render_source_projection(var_debug_info.composite.as_deref()); + let value = self + .ecx + .eval_place_to_op(*place, None) + .map(|op| self.render_source_shaped_op(op)) + .unwrap_or_else(|err| { + format!("", interpret::format_interp_error(err)) + }); + + local_descs.push(LocalDesc { + source_name: Some(var_debug_info.name), + source_projection, + local: Some(place.local), + storage_projection, + ty: place.ty(local_decls, self.ecx.tcx.tcx).ty.to_string(), + value, + }); + } + } + } + + local_descs + } +} + +pub(super) enum DebuggerCommand { + StepI, + Step, + TerminateSession, + Continue, + Breakpoint(PathBuf, usize), + ListLocals, + Print(usize), + Follow(AllocId, usize), +} + +pub(super) enum BreakpointSetResult { + Added(PathBuf, usize), + Duplicate, + // FIXME: add pending breakpoint support later if needed. +} + +pub(super) enum CommandResult { + ExecutionStopped(StepResult), + BreakpointResult(BreakpointSetResult), + Locals(Vec), + SingleLocal(Option), + Memory(String), + // FIXME: distinguish terminating the debugger session from disconnecting a + // frontend and terminating the interpreted program once multiple frontends exist. + TerminateSession, +} diff --git a/src/tools/miri/priroda/src/frontend/cli.rs b/src/tools/miri/priroda/src/frontend/cli.rs new file mode 100644 index 0000000000000..e4e92351a92f6 --- /dev/null +++ b/src/tools/miri/priroda/src/frontend/cli.rs @@ -0,0 +1,176 @@ +use std::io::{self, Write}; +use std::num::NonZeroU64; +use std::path::PathBuf; + +use miri::{InterpResult, interp_ok}; +use rustc_middle::mir::interpret::AllocId; + +use crate::debugger::{ + BreakpointSetResult, CommandResult, DebuggerCommand, PrirodaContext, StepResult, +}; + +pub(crate) struct Cli; + +impl Cli { + pub(crate) fn run_cli_loop<'tcx>( + &self, + session: &mut PrirodaContext<'tcx>, + ) -> InterpResult<'tcx> { + loop { + print!("(priroda) "); + io::stdout().flush().unwrap(); + + let mut input = String::new(); + let bytes_read = io::stdin().read_line(&mut input).unwrap(); + + if bytes_read == 0 { + println!("stdin closed, stopping"); + return interp_ok(()); + } + + if let Some(command) = self.parse_command(&input) { + let command_res = session.run_command(command)?; + if !Self::print_command_result(command_res, session)? { + return interp_ok(()); + }; + } else { + println!("no command"); + } + + io::stdout().flush().unwrap(); + } + } + + fn print_command_result<'tcx>( + command_res: CommandResult, + session: &PrirodaContext<'tcx>, + ) -> InterpResult<'tcx, bool> { + match command_res { + CommandResult::ExecutionStopped(result) => { + if matches!(result, StepResult::Breakpoint) { + println!("Hit breakpoint"); + } + Self::print_location(session); + } + CommandResult::BreakpointResult(res) => + match res { + BreakpointSetResult::Added(path, line) => { + println!("breakpoint added: {}:{}", path.display(), line) + } + + BreakpointSetResult::Duplicate => println!("Duplicate breakpoint"), + }, + CommandResult::Locals(locals_desc) => + if locals_desc.is_empty() { + println!("no locals"); + } else { + for local_desc in &locals_desc { + let source_projection = local_desc.source_projection_str(); + + let name = local_desc + .source_name + .map_or_else(|| "".to_string(), |name| name.to_string()); + + let display_name = format!("{name}{source_projection}"); + + let local_id = local_desc.local.map_or_else( + || "".to_string(), + |local_idx| format!("_{}", local_idx.index()), + ); + + let display_local_id = + format!("{}{}", local_id, local_desc.storage_projection_str()); + println!( + "Name: {}, Id: {}, Ty: {}, Value: {}", + display_name, display_local_id, local_desc.ty, local_desc.value + ); + } + }, + CommandResult::SingleLocal(local_desc) => + match local_desc { + Some(local_desc) => { + println!( + "Id: _{}, Ty: {}, Value: {}", + local_desc.local.unwrap().index(), + local_desc.ty, + local_desc.value + ); + } + None => println!("no local for this id"), + }, + CommandResult::Memory(memory) => println!("{memory}"), + CommandResult::TerminateSession => { + println!("quitting"); + return interp_ok(false); + } + } + interp_ok(true) + } + + fn parse_command(&self, input: &str) -> Option { + // TODO: look at the Spanned crate for how to easily produce errors in + // rustc's style while manually parsing text input. + // FIXME: we need to distinguish malformed input from the unknown commands by returning useful + // command error that describes if it malformed or non exist command + let input = input.trim(); + let mut parts = input.splitn(2, char::is_whitespace); + let command = parts.next().unwrap_or(""); + let args = parts.next().unwrap_or("").trim(); + + match command { + // FIXME: empty line should repats last command user typed not exeute specific command. + "" | "si" | "stepi" => Some(DebuggerCommand::StepI), + "s" | "step" => Some(DebuggerCommand::Step), + "q" | "quit" => Some(DebuggerCommand::TerminateSession), + "c" | "continue" => Some(DebuggerCommand::Continue), + "b" | "break" => self.parse_breakpoint(args), + "l" | "locals" => Some(DebuggerCommand::ListLocals), + "p" | "print" => self.parse_print_local(args), + "f" | "follow" => self.parse_follow(args), + _ => None, + } + } + + fn print_location<'tcx>(session: &PrirodaContext<'tcx>) { + match &session.current_location { + Some(location) => + if let Some(path) = session.local_path(location) { + println!("{}:{}", path.display(), location.line); + } else { + let source_map = session.ecx.tcx.sess.source_map(); + println!("{}", source_map.span_to_diagnostic_string(location.span)); + }, + None => println!("no-location"), + } + io::stdout().flush().unwrap(); + } + + fn parse_breakpoint(&self, input: &str) -> Option { + // FIXME: return a typed CommandError so malformed breakpoint input is + // distinguishable from an unknown command. Semantic validation belongs + // in PrirodaContext::set_breakpoint so non-CLI frontends cannot bypass it. + let (path, line) = input.rsplit_once(':')?; + let line = line.parse().ok()?; + + Some(DebuggerCommand::Breakpoint(PathBuf::from(path), line)) + } + + fn parse_print_local(&self, input: &str) -> Option { + let local = input.parse().ok()?; + Some(DebuggerCommand::Print(local)) + } + + fn parse_follow(&self, input: &str) -> Option { + let mut parts = input.split_whitespace(); + let alloc_id = parts.next()?; + let offset = parts.next()?; + if parts.next().is_some() { + return None; + } + + let alloc_id = alloc_id.strip_prefix("alloc").unwrap_or(alloc_id).parse().ok()?; + let alloc_id = AllocId(NonZeroU64::new(alloc_id)?); + let offset = offset.parse().ok()?; + Some(DebuggerCommand::Follow(alloc_id, offset)) + } +} diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs new file mode 100644 index 0000000000000..75536910aacac --- /dev/null +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -0,0 +1,17 @@ +use miri::{InterpResult, interp_ok}; + +use crate::debugger::PrirodaContext; + +/// Debug Adapter Protocol frontend. +pub(crate) struct Dap; + +impl Dap { + /// Serve DAP requests on stdin/stdout. + pub(crate) fn run_dap_loop<'tcx>( + &self, + _session: &mut PrirodaContext<'tcx>, + ) -> InterpResult<'tcx> { + // FIXME: implement DAP framing and request dispatch on top of PrirodaContext. + interp_ok(()) + } +} diff --git a/src/tools/miri/priroda/src/frontend/mod.rs b/src/tools/miri/priroda/src/frontend/mod.rs new file mode 100644 index 0000000000000..8d2f57fb674f8 --- /dev/null +++ b/src/tools/miri/priroda/src/frontend/mod.rs @@ -0,0 +1,5 @@ +mod cli; +mod dap; + +pub(super) use cli::Cli; +pub(super) use dap::Dap; diff --git a/src/tools/miri/priroda/src/main.rs b/src/tools/miri/priroda/src/main.rs index bb2327c8338e0..9b0efdf9fadb8 100644 --- a/src/tools/miri/priroda/src/main.rs +++ b/src/tools/miri/priroda/src/main.rs @@ -15,26 +15,17 @@ extern crate rustc_session; extern crate rustc_span; extern crate rustc_type_ir; -use std::collections::{HashMap, HashSet}; -use std::io::{self, Write}; -use std::num::NonZeroU64; -use std::ops::Range; -use std::path::PathBuf; +mod debugger; +mod frontend; -use miri::Immediate::Uninit; +use debugger::PrirodaContext; use miri::*; -use rustc_abi::{FIRST_VARIANT, FieldIdx, Size}; use rustc_driver::Compilation; use rustc_hir::attrs::CrateType; -use rustc_hir::def::CtorKind; use rustc_interface::interface; -use rustc_middle::mir::interpret::AllocId; -use rustc_middle::mir::{self, Local, ProjectionElem, VarDebugInfoContents, VarDebugInfoFragment}; -use rustc_middle::ty::{self, TyCtxt, TyKind}; +use rustc_middle::ty::TyCtxt; use rustc_session::EarlyDiagCtxt; use rustc_session::config::ErrorOutputType; -use rustc_span::source_map::SourceMap; -use rustc_span::{Span, Symbol}; fn find_sysroot() -> String { std::env::var("MIRI_SYSROOT") @@ -115,14 +106,8 @@ impl rustc_driver::Callbacks for PrirodaCompilerCalls { let mut session = PrirodaContext::new(ecx); let result = match self.frontend { - Frontend::Cli => { - let cli = Cli {}; - cli.run_cli_loop(&mut session) - } - Frontend::Dap => { - let dap = Dap {}; - dap.run_dap_loop(&mut session) - } + Frontend::Cli => frontend::Cli {}.run_cli_loop(&mut session), + Frontend::Dap => frontend::Dap {}.run_dap_loop(&mut session), }; match result.report_err() { @@ -152,977 +137,3 @@ fn create_ecx<'tcx>(tcx: TyCtxt<'tcx>) -> MiriInterpCx<'tcx> { // FIXME: report interpreter initialization failures instead of panicking. miri::create_ecx(tcx, entry_id, entry_type, &config, None).unwrap() } - -/// Structured source information for frontends. -struct SourceLocation { - // storing `span` to use it lazily to compute path. - span: Span, - line: usize, -} - -impl SourceLocation { - fn local_path(&self, source_map: &SourceMap) -> Option { - let loc = source_map.lookup_char_pos(self.span.lo()); - loc.file.name.clone().into_local_path().map(normalize_path) - } -} - -/// Source-level breakpoints indexed by normalized path, then line. -type BreakpointTable = HashMap>; - -/// Owns one interpreter session and its debugger state. -/// -/// Frontend rendering should eventually live outside this type. -struct PrirodaContext<'tcx> { - ecx: MiriInterpCx<'tcx>, - breakpoints: BreakpointTable, - current_location: Option, - last_location: Option, -} - -enum StorageProj { - Field(usize), - Deref, - Downcast(Symbol), - Variant(usize), - Unsupported(String), -} - -impl StorageProj { - fn render(&self) -> String { - match self { - StorageProj::Field(field_idx) => format!(".{field_idx}"), - StorageProj::Deref => format!(".*"), - StorageProj::Downcast(name) => format!(" as {name}"), - StorageProj::Variant(variant_idx) => format!(" as variant#{variant_idx}"), - StorageProj::Unsupported(unsop) => format!("."), - } - } -} - -struct LocalDesc { - /// Source variable name from `VarDebugInfo`, if this row has one. - source_name: Option, - - /// Source-side projection from `VarDebugInfo::composite`, e.g. `.field` in source fragment `x.field`. - source_projection: Option>, - - /// MIR storage local that backs this description, if any. - local: Option, - - /// rendered/debug MIR place projection for now - storage_projection: Vec, - - /// Display-rendered type for this description. - ty: String, - - /// Run-time state for now; will be expanded later - value: String, -} - -/// Controls when execution returns to the frontend. -enum ResumeMode { - /// Stop at the next visible MIR instruction. - MirInstruction, - /// Stop at the next source line - /// - /// Take `Option` because some cases current state has no mapped to source code location - SourceLine(Option<(PathBuf, usize)>), - /// Continue until reaching a breakpoint. - Continue, -} - -/// Describes whether the current MIR instruction should be shown to the user. -enum InstructionVisibility { - NoInstruction, - Hidden, - Visible, -} - -/// Describes why execution stopped and returned control to the frontend. -enum StepResult { - Step, - Breakpoint, -} - -fn normalize_path(path: PathBuf) -> PathBuf { - path.canonicalize().unwrap_or(path) -} - -impl<'tcx> PrirodaContext<'tcx> { - fn new(ecx: MiriInterpCx<'tcx>) -> Self { - Self { ecx, breakpoints: HashMap::new(), current_location: None, last_location: None } - } - - fn local_path(&self, location: &SourceLocation) -> Option { - let source_map = self.ecx.tcx.sess.source_map(); - location.local_path(source_map) - } - - fn current_source_position(&self) -> Option<(PathBuf, usize)> { - let location = self.current_location.as_ref()?; - Some((self.local_path(location)?, location.line)) - } - - // Used to treat `continue` like a source-level step for breakpoint checks: - // several MIR locations can point at one source line, but they should only - // report that source breakpoint once. - fn last_source_position(&self) -> Option<(PathBuf, usize)> { - let location = self.last_location.as_ref()?; - Some((self.local_path(location)?, location.line)) - } - - /// Step to the next visible MIR instruction. - fn stepi(&mut self) -> InterpResult<'tcx, StepResult> { - self.resume(ResumeMode::MirInstruction) - } - fn step(&mut self) -> InterpResult<'tcx, StepResult> { - self.resume(ResumeMode::SourceLine(self.current_source_position())) - } - - /// Continue execution until reaching a breakpoint or propagating termination. - fn continue_execution(&mut self) -> InterpResult<'tcx, StepResult> { - self.resume(ResumeMode::Continue) - } - - fn set_breakpoint(&mut self, path: PathBuf, line: usize) -> BreakpointSetResult { - // FIXME: validate breakpoints here so every frontend gets the same behavior. - // Reject empty paths, missing files, directories, and line 0. Decide whether - // out-of-range lines should be rejected or kept as pending breakpoints. - // Report duplicate registrations separately. - - let path = normalize_path(path); - match self.breakpoints.entry(path.clone()).or_default().insert(line) { - true => BreakpointSetResult::Added(path, line), - false => BreakpointSetResult::Duplicate, - } - } - - /// Advance execution until the selected resume mode reaches a stopping point. - fn resume(&mut self, mode: ResumeMode) -> InterpResult<'tcx, StepResult> { - loop { - self.advance()?; - - // An explicit breakpoint should stop execution even when the current - // MIR instruction would normally be hidden during manual stepping. - if self.is_at_breakpoint() { - return interp_ok(StepResult::Breakpoint); - } - - match mode { - ResumeMode::MirInstruction - if matches!( - self.current_instruction_visibility(), - InstructionVisibility::Visible - ) => - { - return interp_ok(StepResult::Step); - } - - ResumeMode::SourceLine(ref prev_location) => { - match (prev_location, &self.current_location) { - // We started from an unmapped source location. Stop at the first mapped source location we can show to the user. - (None, Some(_)) => return interp_ok(StepResult::Step), - - (Some((prev_path, prev_line)), Some(current_location)) => { - if let Some(current_path) = self.local_path(current_location) { - // A source step stops when the visible source position changes to a different file or line. - if *prev_path != current_path || *prev_line != current_location.line - { - return interp_ok(StepResult::Step); - } - } - } - - _ => {} - } - } - - ResumeMode::MirInstruction | ResumeMode::Continue => {} - } - } - } - - /// Advance Miri by one interpreter-loop transition. - fn advance(&mut self) -> InterpResult<'tcx> { - // FIXME: use a Miri-owned scheduler-aware debugger step API before - // claiming support for multi-threaded interpreted programs. - - // State inspection should happen only after a successful step. - self.ecx.step_current_thread()?; - self.last_location = self.current_location.take(); - self.current_location = self.resolve_current_location(); - interp_ok(()) - } - - fn current_instruction_visibility(&self) -> InstructionVisibility { - // If the active thread has no stack frame, there is no MIR instruction to show. - let Some(frame) = self.ecx.active_thread_stack().last() else { - return InstructionVisibility::NoInstruction; - }; - - // `Right(span)` means the frame has source context but no precise MIR program-counter location. - let Either::Left(location) = frame.current_loc() else { - return InstructionVisibility::NoInstruction; - }; - - let basic_block = &frame.body().basic_blocks[location.block]; - - // `statement_index == statements.len()` points at the block terminator. - // Terminators affect control flow, so they are always visible. - let Some(statement) = basic_block.statements.get(location.statement_index) else { - return InstructionVisibility::Visible; - }; - - // Hide bookkeeping-only MIR statements during manual stepping. - match statement.kind { - mir::StatementKind::StorageLive(_) - | mir::StatementKind::StorageDead(_) - | mir::StatementKind::Nop => InstructionVisibility::Hidden, - _ => InstructionVisibility::Visible, - } - } - - fn is_at_breakpoint(&self) -> bool { - let Some(bp) = self.current_breakpoint() else { - return false; - }; - - // If the previous interpreter step had the same source position, this - // is another MIR location for the breakpoint we just reported. - self.last_source_position().as_ref() != Some(&bp) - } - - fn current_breakpoint(&self) -> Option<(PathBuf, usize)> { - let (path, line) = self.current_source_position()?; - let lines = self.breakpoints.get(&path)?; - - if lines.contains(&line) { Some((path, line)) } else { None } - } - - fn resolve_current_location(&self) -> Option { - // FIXME: resolve macro-backed lines such as `println!` and `assert_eq!` - // through `span.source_callsite()` before matching breakpoints. - let span = self.ecx.machine.current_user_relevant_span(); - if span.is_dummy() { - return None; - } - - let source_map = self.ecx.tcx.sess.source_map(); - let loc = source_map.lookup_char_pos(span.lo()); - - Some(SourceLocation { span, line: loc.line }) - } - - fn run_command(&mut self, command: DebuggerCommand) -> InterpResult<'tcx, CommandResult> { - match command { - DebuggerCommand::StepI => self.stepi().map(CommandResult::ExecutionStopped), - DebuggerCommand::Step => self.step().map(CommandResult::ExecutionStopped), - DebuggerCommand::Continue => - self.continue_execution().map(CommandResult::ExecutionStopped), - DebuggerCommand::Breakpoint(path, line) => - interp_ok(CommandResult::BreakpointResult(self.set_breakpoint(path, line))), - DebuggerCommand::ListLocals => interp_ok(CommandResult::Locals(self.list_locals())), - DebuggerCommand::Print(local) => - interp_ok(CommandResult::SingleLocal(self.get_local(local))), - DebuggerCommand::Follow(alloc_id, offset) => - self.follow_alloc(alloc_id, offset).map(CommandResult::Memory), - DebuggerCommand::TerminateSession => interp_ok(CommandResult::TerminateSession), - } - } - - fn follow_alloc(&self, alloc_id: AllocId, offset: usize) -> InterpResult<'tcx, String> { - let alloc = self.ecx.get_alloc_raw(alloc_id)?; - if offset > alloc.len() { - return Err(miri::err_unsup_format!( - "allocation offset {offset} is outside {alloc_id}" - )) - .into(); - } - - let memory = self.render_alloc_bytes(alloc_id, offset..alloc.len())?; - interp_ok(format!("Allocation {alloc_id}+{offset}: {memory}")) - } - - fn get_local(&self, local: usize) -> Option { - let frame = self.ecx.active_thread_stack().last()?; - - self.make_mir_local_desc(frame, local) - } - - /// Returns structured descriptions for locals in the innermost stack frame. - /// - /// Starts from all MIR locals, then enriches them with source names from - /// `var_debug_info` when a debug entry maps directly to a whole local. - fn list_locals(&self) -> Vec { - let Some(frame) = self.ecx.active_thread_stack().last() else { - return Vec::new(); - }; - - self.build_local_descs(frame) - } - - /// Renders the current byte range of an indirect MIR value. - /// - /// Initialized bytes are shown in hexadecimal, uninitialized bytes as `??`, - /// and complete pointer-sized provenance as pointer markers. - fn render_mplace_bytes(&self, mplace: &MPlaceTy<'tcx>) -> InterpResult<'tcx, String> { - let size = match self.ecx.size_and_align_of_val(mplace)? { - Some((size, _)) => size, - None => { - // Extern types cannot currently be executed as by-value locals, - // so this path cannot yet be covered by a Priroda UI fixture. - // FIXME: Add coverage once Priroda supports printing dereferenced places. - return interp_ok("".to_string()); - } - }; - - let size = size.bytes_usize(); - if size == 0 { - return interp_ok("[]".to_string()); - } - - let (alloc_id, offset, _) = - self.ecx.ptr_get_alloc_id(mplace.ptr(), size.try_into().unwrap())?; - let offset = offset.bytes_usize(); - let range = offset..offset.strict_add(size); - - self.render_alloc_bytes(alloc_id, range) - } - - /// Render a raw allocation range without requiring a typed memory place. - /// - /// This is also used by the future-facing `follow` command, where we have a - /// pointer target but do not yet know the target's type or size. - fn render_alloc_bytes( - &self, - alloc_id: AllocId, - range: Range, - ) -> InterpResult<'tcx, String> { - let alloc = self.ecx.get_alloc_raw(alloc_id)?; - - let mut rendered = Vec::with_capacity(range.len()); - - let ptr_size = self.ecx.tcx.data_layout.pointer_size(); - - for chunk in alloc.init_mask().range_as_init_chunks(range.into()) { - let chunk_range = chunk.range(); - let chunk_range = chunk_range.start.bytes_usize()..chunk_range.end.bytes_usize(); - - if chunk.is_init() { - let ptr_size = ptr_size.bytes_usize(); - let mut cursor = chunk_range.start; - - while cursor < chunk_range.end { - // Full pointer provenance is rendered as a pointer marker. Bytewise - // provenance fragments are intentionally left as raw bytes here: they do - // not represent a complete pointer-sized value. - if let Some(prov) = alloc.provenance().get_ptr(Size::from_bytes(cursor)) - && cursor + ptr_size <= chunk_range.end - { - let bytes = alloc.inspect_with_uninit_and_ptr_outside_interpreter( - cursor..cursor + ptr_size, - ); - let offset = read_target_uint(self.ecx.tcx.data_layout.endian, bytes) - .map_err(|err| { - miri::err_unsup_format!("invalid pointer representation: {err}") - })?; - - let offset = Size::from_bytes(offset); - rendered.push(format!("{:?}", Pointer::new(Some(prov), offset))); - - cursor += ptr_size; - } else { - let byte = alloc - .inspect_with_uninit_and_ptr_outside_interpreter(cursor..cursor + 1)[0]; - - rendered.push(format!("{byte:02x}")); - cursor += 1; - } - } - } else { - rendered.extend(std::iter::repeat_n("__".to_string(), chunk_range.len())); - } - } - - interp_ok(format!("[{}]", rendered.join(" "))) - } - - /// Render an evaluated operand using Rust-source-shaped containers with raw leaves. - /// - /// The operand is produced from live interpreter state, usually via `local_to_op` - /// for a whole MIR local or `eval_place_to_op` for a projected debug-info place. - /// - /// This intentionally does not call user `Debug` / `Display`, and it does not - /// try to make every scalar leaf pretty yet. Unsupported cases and leaf values - /// fall back to `render_op`, preserving the old raw byte/provenance renderer. - /// - /// FIXME: teach the leaf renderer about simple Rust scalars (`bool`, integers, - /// chars, raw pointers/references) once the source-shaped container output is - /// stable enough to stop depending on byte dumps for every field. - /// - /// FIXME: decide how much dereferencing belongs in this renderer. References - /// currently stay as raw pointer leaves; following them may belong in the - /// existing `follow` command instead of automatic local rendering. - fn render_source_shaped_op(&self, op: OpTy<'tcx>) -> String { - self.render_source_shaped_op_inner(op, 0) - } - - /// Recursive worker for `render_source_shaped_op`. - /// - /// The depth limit keeps cyclic/reference-heavy values from making debugger - /// output explode once more container kinds are added. At the limit, the raw - /// renderer remains the ground truth. - /// - /// FIXME: replace this fixed recursion limit with a value-size/output-budget - /// policy so large acyclic values and deeply nested values degrade more - /// predictably. - fn render_source_shaped_op_inner(&self, op: OpTy<'tcx>, depth: usize) -> String { - const MAX_SOURCE_SHAPE_DEPTH: usize = 8; - - if depth >= MAX_SOURCE_SHAPE_DEPTH { - return self.render_op(op); - } - - match op.layout.ty.kind() { - // Empty enums have no active variant to format. Unions do not record - // which field is currently active, so choosing one would be misleading. - // - // FIXME: support unions only with an explicit user-selected field or - // another source of active-field information. Guessing from layout - // bytes would make debugger output look more certain than it is. - ty::Adt(def, _) if def.variants().is_empty() || def.is_union() => self.render_op(op), - - ty::Adt(def, _) => { - // Enums need their runtime discriminant and a downcasted layout - // view before fields can be projected. Structs use their sole - // variant directly. Keep the display name tied to the same choice. - let (variant_idx, down, name) = if def.is_enum() { - let variant_idx = match self.ecx.read_discriminant(&op).discard_err() { - Some(variant_idx) => variant_idx, - // FIXME: expose this as an explicit render error when - // Priroda grows structured value states. Falling back to - // bytes keeps today's UI usable but hides why the enum - // could not be source-shaped. - None => return self.render_op(op), - }; - let down = match self.ecx.project_downcast(&op, variant_idx).discard_err() { - Some(down) => down, - // FIXME: distinguish invalid/uninitialized discriminants - // from projection bugs in the rendered output once locals - // can carry structured diagnostics. - None => return self.render_op(op), - }; - let variant_def = &def.variants()[variant_idx]; - ( - variant_idx, - down, - format!("{}::{}", self.ecx.tcx.item_name(def.did()), variant_def.name), - ) - } else { - let variant_idx = FIRST_VARIANT; - let variant_def = &def.variants()[variant_idx]; - (variant_idx, op.clone(), variant_def.name.to_string()) - }; - - let variant_def = &def.variants()[variant_idx]; - - let mut fields = Vec::with_capacity(variant_def.fields.len()); - for i in 0..variant_def.fields.len() { - let field_idx = FieldIdx::from_usize(i); - // `project_field` avoids manual offset math and works for both - // immediate and memory-backed operands through `Projectable`. - let field_op = match self.ecx.project_field(&down, field_idx).discard_err() { - Some(field_op) => field_op, - // FIXME: preserve the successfully rendered fields and - // mark only this field as unavailable once the value model - // can represent partial render failures. - None => return self.render_op(op), - }; - fields.push(self.render_source_shaped_op_inner(field_op, depth + 1)); - } - - // Match Rust constructor spelling: - // - `Const`: unit structs/variants, e.g. `UnitStruct`, `Enum::Unit` - // - `Fn`: tuple structs/variants, e.g. `Pair(a, b)` or `EmptyTuple()` - // - `None`: braced structs/variants, including the empty `{}` case - match variant_def.ctor_kind() { - Some(CtorKind::Const) => name, - Some(CtorKind::Fn) => format!("{name}({})", fields.join(", ")), - None if fields.is_empty() => format!("{name} {{}}"), - None => { - let fields = variant_def - .fields - .iter() - .zip(fields) - .map(|(field_def, value)| format!("{}: {value}", field_def.name)) - .collect::>() - .join(", "); - format!("{name} {{ {fields} }}") - } - } - } - - ty::Tuple(args) => { - let mut fields = Vec::with_capacity(args.len()); - for i in 0..args.len() { - // Tuples have no field names in source, so preserve their - // source field order and render children positionally. - let field_op = - match self.ecx.project_field(&op, FieldIdx::from_usize(i)).discard_err() { - Some(field_op) => field_op, - // FIXME: render tuple fields independently so one - // projection failure does not throw away the whole - // source-shaped tuple. - None => return self.render_op(op), - }; - fields.push(self.render_source_shaped_op_inner(field_op, depth + 1)); - } - - if fields.len() == 1 { - format!("({},)", fields[0]) - } else { - format!("({})", fields.join(", ")) - } - } - - ty::Array(_, _) | ty::Slice(_) => { - // `project_array_fields` uses the dynamic length for slices. That - // avoids the classic mistake of treating slice layout as a fixed - // zero-length array. - let mut iter = match self.ecx.project_array_fields(&op).discard_err() { - Some(iter) => iter, - // FIXME: when slice metadata is invalid, show that as a slice - // length problem instead of silently falling back to raw bytes. - None => return self.render_op(op), - }; - - let mut fields = Vec::new(); - // FIXME: add an output budget/truncation policy before rendering - // very large arrays or slices in full. - loop { - match iter.next(&self.ecx).discard_err() { - Some(Some((_idx, field_op))) => - fields.push(self.render_source_shaped_op_inner(field_op, depth + 1)), - Some(None) => break, - // FIXME: keep already-rendered elements and mark the - // failed index once partial render errors are supported. - None => return self.render_op(op), - } - } - - format!("[{}]", fields.join(", ")) - } - - // FIXME: consider source-shaped special cases for strings, closures, - // generators/coroutines, trait objects, and SIMD/vector-like types. - // Until then these stay on the raw renderer path. - _ => self.render_op(op), - } - } - - /// Render an evaluated operand using the same raw representation for - /// whole locals and projected MIR places. - fn render_op(&self, op: OpTy<'tcx>) -> String { - match op.as_mplace_or_imm() { - Either::Right(imm) => format!("{imm}"), - - Either::Left(mplace) => - match self.render_mplace_bytes(&mplace).report_err() { - Ok(bytes) => bytes, - Err(err) => format!("", err.to_string()), - }, - } - } - - /// Render the source-side path from composite debug info, such as `.field`. - fn render_source_projection( - fragment: Option<&VarDebugInfoFragment<'tcx>>, - ) -> Option> { - let VarDebugInfoFragment { ty, projection } = fragment?; - - // Walk the source-side projection from the original - // composite variable type. Each `Field` element stores the - // resulting field type, so resolve the field name from the - // current base type before advancing to `field_ty`. - let mut projection_ty = ty; - - Some( - projection - .iter() - .map(|elem| { - match elem { - ProjectionElem::Field(field_idx, field_ty) => { - let rendered = match projection_ty.kind() { - TyKind::Adt(adt_def, _args) if adt_def.is_struct() => { - let variant = adt_def.non_enum_variant(); - let field = &variant.fields[*field_idx]; - Symbol::intern(&format!(".{}", field.name)) - } - - TyKind::Tuple(_) => - Symbol::intern(&format!(".{}", field_idx.index())), - - _ => Symbol::intern("."), - }; - - projection_ty = field_ty; - - rendered - } - // `VarDebugInfoFragment::projection` is expected to be - // field-only. If that ever changes, keep the unexpected - // segment visible instead of silently rendering a - // misleading source path. - other => Symbol::intern(&format!(".")), - } - }) - .collect(), - ) - } - - /// Render the MIR storage-side path that backs a debug-info local. - fn render_storage_projection(projection: &[mir::PlaceElem<'tcx>]) -> Vec { - projection - .iter() - .map(|projection_elem| { - match projection_elem { - ProjectionElem::Field(field_idx, _) => StorageProj::Field(field_idx.index()), - ProjectionElem::Deref => StorageProj::Deref, - ProjectionElem::Downcast(Some(name), _) => StorageProj::Downcast(*name), - ProjectionElem::Downcast(None, variant_idx) => - StorageProj::Variant(variant_idx.index()), - other => StorageProj::Unsupported(format!("{other:?}")), - } - }) - .collect() - } - - /// Builds the baseline debugger row for one MIR local without scanning debug info. - fn make_mir_local_desc( - &self, - frame: &Frame<'tcx, Provenance, FrameExtra<'tcx>>, - local: usize, - ) -> Option { - let local = mir::Local::from_usize(local); - let local_decl = frame.body().local_decls.get(local)?; - - // Create LocalDesc for MIR local before processing debug info. - // Debug-info enrichment is layered on by build_local_descs. - let mut local_desc = LocalDesc { - source_name: None, - source_projection: None, - local: Some(local), - storage_projection: Vec::new(), - ty: local_decl.ty.to_string(), - value: "".to_string(), - }; - - match &frame.locals[local].as_mplace_or_imm() { - None => { - local_desc.value = "".to_string(); - } - Some(Either::Right(Uninit)) => local_desc.value = "".to_string(), - - Some(Either::Left(_) | Either::Right(_)) => { - let op = self - .ecx - .local_to_op(local, None) - .expect("this error can only occur in CTFE on generic code"); - local_desc.value = self.render_source_shaped_op(op); - } - }; - - Some(local_desc) - } - - fn build_local_descs( - &self, - frame: &Frame<'tcx, Provenance, FrameExtra<'tcx>>, - ) -> Vec { - let local_decls = &frame.body().local_decls; - - let mut local_descs: Vec = Vec::with_capacity(local_decls.len()); - - // Start with one baseline row for every MIR local, then layer debug info on top. - for (local_idx, _) in local_decls.iter_enumerated() { - local_descs.push(self.make_mir_local_desc(frame, local_idx.index()).unwrap()); - } - - // FIXME: Finish classifying `var_debug_info` by keeping the source path - // and MIR storage path separate: - // - // - source side: `var_debug_info.name` plus - // `var_debug_info.composite.projection` - // - storage side: `VarDebugInfoContents::Place(place).local` plus - // `place.projection` - // - // Already handled by the `place.as_local()` path below: - // - whole source variable -> whole MIR local: - // `composite = None`, `Place(_N)` with empty projection. - // - source fragment -> whole MIR local: - // `composite = Some(source_proj)`, `Place(_N)` with empty projection. - // - // Remaining cases to represent or explicitly defer: - // - whole source variable -> projected MIR storage: - // `composite = None`, `Place(_N.proj)`. - // - source fragment -> projected MIR storage: - // `composite = Some(source_proj)`, `Place(_N.storage_proj)`. - // - source variable/fragment -> constant: - // `Const(...)`, with no MIR local id. - // - optimized-out/debug-only/unsupported shapes: - // explicit deferred state, not silent discard. - // - // Final output should be produced by walking `Vec`, - // then append explicit deferred/debug-info-only rows where needed. - // Related: SROA can split a source local like `_slice: ExtraSlice` into - // field locals whose debug paths should be printed as `_slice._slice` - // and `_slice._extra`, not as two separate locals both named `_slice`. - - // Whole-place debug entries enrich the direct storage-local description. - // Projected places are evaluated from their original MIR Place and use - // the same raw renderer as ordinary locals. - for var_debug_info in &frame.body().var_debug_info { - if let VarDebugInfoContents::Place(place) = &var_debug_info.value { - if let Some(local_idx) = place.as_local() - && local_descs[local_idx.index()].source_name.is_none() - { - let local_idx = local_idx.index(); - local_descs[local_idx].source_projection = - Self::render_source_projection(var_debug_info.composite.as_deref()); - local_descs[local_idx].source_name = Some(var_debug_info.name); - } else if !place.projection.is_empty() { - let storage_projection = Self::render_storage_projection(place.projection); - let source_projection = - Self::render_source_projection(var_debug_info.composite.as_deref()); - let value = self - .ecx - .eval_place_to_op(*place, None) - .map(|op| self.render_source_shaped_op(op)) - .unwrap_or_else(|err| format!("", err.to_string())); - - local_descs.push(LocalDesc { - source_name: Some(var_debug_info.name), - source_projection, - local: Some(place.local), - storage_projection, - ty: place.ty(local_decls, self.ecx.tcx.tcx).ty.to_string(), - value, - }); - } - } - } - - local_descs - } -} - -enum DebuggerCommand { - StepI, - Step, - TerminateSession, - Continue, - Breakpoint(PathBuf, usize), - ListLocals, - Print(usize), - Follow(AllocId, usize), -} - -enum BreakpointSetResult { - Added(PathBuf, usize), - Duplicate, - // FIXME: add pending breakpoint support later if needed. -} - -enum CommandResult { - ExecutionStopped(StepResult), - BreakpointResult(BreakpointSetResult), - Locals(Vec), - SingleLocal(Option), - Memory(String), - // FIXME: distinguish terminating the debugger session from disconnecting a - // frontend and terminating the interpreted program once multiple frontends exist. - TerminateSession, -} - -struct Cli; - -impl Cli { - pub fn run_cli_loop<'tcx>(&self, session: &mut PrirodaContext<'tcx>) -> InterpResult<'tcx> { - loop { - print!("(priroda) "); - io::stdout().flush().unwrap(); - - let mut input = String::new(); - let bytes_read = io::stdin().read_line(&mut input).unwrap(); - - if bytes_read == 0 { - println!("stdin closed, stopping"); - return interp_ok(()); - } - - if let Some(command) = self.parse_command(&input) { - let command_res = session.run_command(command)?; - if !Self::print_command_result(command_res, session)? { - return interp_ok(()); - }; - } else { - println!("no command"); - } - - io::stdout().flush().unwrap(); - } - } - - fn print_command_result<'tcx>( - command_res: CommandResult, - session: &PrirodaContext<'tcx>, - ) -> InterpResult<'tcx, bool> { - match command_res { - CommandResult::ExecutionStopped(result) => { - if matches!(result, StepResult::Breakpoint) { - println!("Hit breakpoint"); - } - Self::print_location(session); - } - CommandResult::BreakpointResult(res) => - match res { - BreakpointSetResult::Added(path, line) => - println!("breakpoint added: {}:{}", path.display(), line), - - BreakpointSetResult::Duplicate => println!("Duplicate breakpoint"), - }, - CommandResult::Locals(locals_desc) => - if locals_desc.is_empty() { - println!("no locals"); - } else { - for local_desc in &locals_desc { - let source_projection = local_desc - .source_projection - .as_ref() - .map(|fields| { - fields.iter().map(|field| field.to_string()).collect::() - }) - .unwrap_or_default(); - - let name = local_desc - .source_name - .map_or_else(|| "".to_string(), |name| name.to_string()); - - let display_name = format!("{name}{source_projection}"); - - let local_id = local_desc.local.map_or_else( - || "".to_string(), - |local_idx| format!("_{}", local_idx.index()), - ); - - let storage_projection = local_desc - .storage_projection - .iter() - .map(StorageProj::render) - .collect::(); - - let display_local_id = format!("{local_id}{storage_projection}"); - println!( - "Name: {}, Id: {}, Ty: {}, Value: {}", - display_name, display_local_id, local_desc.ty, local_desc.value - ); - } - }, - CommandResult::SingleLocal(local_desc) => - match local_desc { - Some(local_desc) => { - println!( - "Id: _{}, Ty: {}, Value: {}", - local_desc.local.unwrap().index(), - local_desc.ty, - local_desc.value - ); - } - None => println!("no local for this id"), - }, - CommandResult::Memory(memory) => println!("{memory}"), - CommandResult::TerminateSession => { - println!("quitting"); - return interp_ok(false); - } - } - interp_ok(true) - } - - fn parse_command(&self, input: &str) -> Option { - // TODO: look at the Spanned crate for how to easily produce errors in - // rustc's style while manually parsing text input. - // FIXME: we need to distinguish malformed input from the unknown commands by returning useful - // command error that describes if it malformed or non exist command - let input = input.trim(); - let mut parts = input.splitn(2, char::is_whitespace); - let command = parts.next().unwrap_or(""); - let args = parts.next().unwrap_or("").trim(); - - match command { - // FIXME: empty line should repats last command user typed not exeute specific command. - "" | "si" | "stepi" => Some(DebuggerCommand::StepI), - "s" | "step" => Some(DebuggerCommand::Step), - "q" | "quit" => Some(DebuggerCommand::TerminateSession), - "c" | "continue" => Some(DebuggerCommand::Continue), - "b" | "break" => self.parse_breakpoint(args), - "l" | "locals" => Some(DebuggerCommand::ListLocals), - "p" | "print" => self.parse_print_local(args), - "f" | "follow" => self.parse_follow(args), - _ => None, - } - } - - fn print_location(session: &PrirodaContext) { - match &session.current_location { - Some(location) => - if let Some(path) = session.local_path(location) { - println!("{}:{}", path.display(), location.line); - } else { - let source_map = session.ecx.tcx.sess.source_map(); - println!("{}", source_map.span_to_diagnostic_string(location.span)); - }, - None => println!("no-location"), - } - io::stdout().flush().unwrap(); - } - - fn parse_breakpoint(&self, input: &str) -> Option { - // FIXME: return a typed CommandError so malformed breakpoint input is - // distinguishable from an unknown command. Semantic validation belongs - // in PrirodaContext::set_breakpoint so non-CLI frontends cannot bypass it. - let (path, line) = input.rsplit_once(':')?; - let line = line.parse().ok()?; - - Some(DebuggerCommand::Breakpoint(PathBuf::from(path), line)) - } - - fn parse_print_local(&self, input: &str) -> Option { - let local = input.parse().ok()?; - Some(DebuggerCommand::Print(local)) - } - - fn parse_follow(&self, input: &str) -> Option { - let mut parts = input.split_whitespace(); - let alloc_id = parts.next()?; - let offset = parts.next()?; - if parts.next().is_some() { - return None; - } - - let alloc_id = alloc_id.strip_prefix("alloc").unwrap_or(alloc_id).parse().ok()?; - let alloc_id = AllocId(NonZeroU64::new(alloc_id)?); - let offset = offset.parse().ok()?; - Some(DebuggerCommand::Follow(alloc_id, offset)) - } -} - -struct Dap; - -impl Dap { - pub fn run_dap_loop<'tcx>(&self, _session: &mut PrirodaContext<'tcx>) -> InterpResult<'tcx> { - // FIXME: implement DAP framing and request dispatch on top of PrirodaContext. - interp_ok(()) - } -} From a6a7a9d757fd9a5529e7df4b89fdbf2627878261 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Thu, 30 Jul 2026 00:24:01 +0300 Subject: [PATCH 022/100] [Priroda] Add minimal DAP initialize handshake --- src/tools/miri/priroda/Cargo.lock | 12 +++ src/tools/miri/priroda/Cargo.toml | 1 + src/tools/miri/priroda/src/frontend/dap.rs | 95 ++++++++++++++++++- src/tools/miri/priroda/tests/cli.rs | 3 + .../priroda/tests/ui/dap_initialize.stdout | 3 + .../dap_rejects_non_initialize_first.stderr | 1 + .../dap_rejects_non_initialize_first.stdout | 3 + 7 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stderr diff --git a/src/tools/miri/priroda/Cargo.lock b/src/tools/miri/priroda/Cargo.lock index 7d46f75d2fab7..48ba54ef8eebf 100644 --- a/src/tools/miri/priroda/Cargo.lock +++ b/src/tools/miri/priroda/Cargo.lock @@ -351,6 +351,17 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "emmy_dap_types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2310ff06ab812a0332ffa037bbda9d994b3721a7f8a308ff38c28bdb20c37f56" +dependencies = [ + "serde", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "encode_unicode" version = "1.0.0" @@ -891,6 +902,7 @@ dependencies = [ name = "priroda" version = "0.1.0" dependencies = [ + "emmy_dap_types", "miri", "regex", "ui_test", diff --git a/src/tools/miri/priroda/Cargo.toml b/src/tools/miri/priroda/Cargo.toml index 88e65653449c8..ff299bae2acbf 100644 --- a/src/tools/miri/priroda/Cargo.toml +++ b/src/tools/miri/priroda/Cargo.toml @@ -18,6 +18,7 @@ name = "cli" harness = false [dependencies] +emmy_dap_types = "0.2.0" miri = { path = ".." } [package.metadata.rust-analyzer] diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index 75536910aacac..31a1447aa3642 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -1,7 +1,19 @@ +use std::io::{self, BufReader, BufWriter}; + +use emmy_dap_types::prelude::types::Capabilities; +use emmy_dap_types::prelude::{Command, Request, ResponseBody, Server}; use miri::{InterpResult, interp_ok}; use crate::debugger::PrirodaContext; +const MAX_REQUEST_COUNT: usize = 128; +type ServerResult = Result; + +enum DispatchOutcome { + Continue, + Exit, +} + /// Debug Adapter Protocol frontend. pub(crate) struct Dap; @@ -11,7 +23,88 @@ impl Dap { &self, _session: &mut PrirodaContext<'tcx>, ) -> InterpResult<'tcx> { - // FIXME: implement DAP framing and request dispatch on top of PrirodaContext. + // FIXME: make this unbounded once Priroda has a full session lifecycle. + if let Err(err) = DapSession::stdio().run_requests() { + eprintln!("priroda dap error: {err}"); + } + interp_ok(()) } } + +type DapServer = Server, io::StdoutLock<'static>>; + +/// Owns the DAP stdio transport and dispatches requests into Priroda handlers. +struct DapSession { + server: DapServer, +} + +impl DapSession { + fn stdio() -> Self { + Self { + server: Server::new( + BufReader::new(io::stdin().lock()), + BufWriter::new(io::stdout().lock()), + ), + } + } + + fn run_requests(&mut self) -> ServerResult { + for _ in 0..MAX_REQUEST_COUNT { + let Some(request) = self.server.poll_request()? else { + return Ok(()); + }; + + match self.dispatch_request(request)? { + DispatchOutcome::Continue => {} + DispatchOutcome::Exit => return Ok(()), + } + } + + Ok(()) + } + + fn dispatch_request(&mut self, request: Request) -> ServerResult { + match &request.command { + Command::Initialize(_) => + self.handle_initialize(request).map(|()| DispatchOutcome::Continue), + _ => self.handle_unsupported_request(request).map(|()| DispatchOutcome::Exit), + } + } + + /// FIXME: grow capabilities as Priroda adds DAP features. + fn handle_initialize(&mut self, request: Request) -> ServerResult { + // Advertise configurationDone support ahead of its handler so VS Code + // completes the full handshake; the handler arrives in a later commit. + let response = request.success(ResponseBody::Initialize(Capabilities { + supports_configuration_done_request: Some(true), + ..Capabilities::default() + })); + self.server.respond(response) + } + + fn handle_unsupported_request(&mut self, request: Request) -> ServerResult { + eprintln!( + "priroda dap: unsupported request during DAP demo milestone: {}", + Self::display_command(&request.command) + ); + let response = request.error("unsupported request in Priroda DAP demo mode"); + self.server.respond(response) + } + + fn display_command(command: &Command) -> &'static str { + match command { + Command::Initialize(_) => "initialize", + Command::Launch(_) => "launch", + Command::ConfigurationDone => "configurationDone", + Command::Threads => "threads", + Command::StackTrace(_) => "stackTrace", + Command::Scopes(_) => "scopes", + Command::Variables(_) => "variables", + Command::Next(_) => "next", + Command::StepIn(_) => "stepIn", + Command::Disconnect(_) => "disconnect", + _ => "unsupported", + } + } +} diff --git a/src/tools/miri/priroda/tests/cli.rs b/src/tools/miri/priroda/tests/cli.rs index 3b596fbf91f26..ff2ce7716348a 100644 --- a/src/tools/miri/priroda/tests/cli.rs +++ b/src/tools/miri/priroda/tests/cli.rs @@ -33,11 +33,14 @@ fn main() -> Result<(), Box> { let miri_dir_regex = Regex::new(®ex::escape(&miri_dir.display().to_string())).unwrap(); let rustc_sysroot_regex = Regex::new(®ex::escape(&rustc_sysroot)).unwrap(); let pointer_regex = Regex::new(r"0x[0-9a-f]+\[alloc[0-9]+\]<[0-9]+>").unwrap(); + let crlf_regex = Regex::new(r"\r\n").unwrap(); config.comment_defaults.base().normalize_stdout.extend([ (manifest_dir_regex.into(), b"{MANIFEST_DIR}".to_vec()), (miri_dir_regex.into(), b"{MIRI_DIR}".to_vec()), (rustc_sysroot_regex.into(), b"{RUSTC_SYSROOT}".to_vec()), (pointer_regex.into(), b"{ALLOC_PTR}".to_vec()), + // DAP frames use CRLF headers; keep checked-in stdout fixtures readable. + (crlf_regex.into(), b"\n".to_vec()), ]); // Priroda CLI tests do not currently require annotation comments in the test files diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize.stdout b/src/tools/miri/priroda/tests/ui/dap_initialize.stdout index e69de29bb2d1d..4773b876a1df8 100644 --- a/src/tools/miri/priroda/tests/ui/dap_initialize.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_initialize.stdout @@ -0,0 +1,3 @@ +Content-Length: 143 + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stderr b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stderr new file mode 100644 index 0000000000000..2641eb804868c --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stderr @@ -0,0 +1 @@ +priroda dap: unsupported request during DAP demo milestone: next diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout index e69de29bb2d1d..18ed56545ac9d 100644 --- a/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout @@ -0,0 +1,3 @@ +Content-Length: 146 + +{"seq":1,"type":"response","request_seq":2,"success":false,"message":"unsupported request in Priroda DAP demo mode","command":"next","error":null} \ No newline at end of file From 0158e09b0938e2992cf73b3ac94de922cf93ab39 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sat, 1 Aug 2026 02:26:36 +0300 Subject: [PATCH 023/100] [Priroda] Add DAP initialized and launch handshake --- src/tools/miri/priroda/src/frontend/dap.rs | 12 ++++++++++-- .../miri/priroda/tests/ui/dap_initialize.stdout | 4 +++- .../miri/priroda/tests/ui/dap_initialize_launch.rs | 3 +++ .../priroda/tests/ui/dap_initialize_launch.stdin | 5 +++++ .../priroda/tests/ui/dap_initialize_launch.stdout | 7 +++++++ 5 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 src/tools/miri/priroda/tests/ui/dap_initialize_launch.rs create mode 100644 src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdin create mode 100644 src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdout diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index 31a1447aa3642..9d6d77f2a8d44 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -1,7 +1,7 @@ use std::io::{self, BufReader, BufWriter}; use emmy_dap_types::prelude::types::Capabilities; -use emmy_dap_types::prelude::{Command, Request, ResponseBody, Server}; +use emmy_dap_types::prelude::{Command, Event, Request, ResponseBody, Server}; use miri::{InterpResult, interp_ok}; use crate::debugger::PrirodaContext; @@ -68,10 +68,17 @@ impl DapSession { match &request.command { Command::Initialize(_) => self.handle_initialize(request).map(|()| DispatchOutcome::Continue), + Command::Launch(_) => self.handle_launch(request).map(|()| DispatchOutcome::Continue), _ => self.handle_unsupported_request(request).map(|()| DispatchOutcome::Exit), } } + /// FIXME: connect launch arguments to Priroda's session model. + fn handle_launch(&mut self, request: Request) -> ServerResult { + let response = request.success(ResponseBody::Launch); + self.server.respond(response) + } + /// FIXME: grow capabilities as Priroda adds DAP features. fn handle_initialize(&mut self, request: Request) -> ServerResult { // Advertise configurationDone support ahead of its handler so VS Code @@ -80,7 +87,8 @@ impl DapSession { supports_configuration_done_request: Some(true), ..Capabilities::default() })); - self.server.respond(response) + self.server.respond(response)?; + self.server.send_event(Event::Initialized) } fn handle_unsupported_request(&mut self, request: Request) -> ServerResult { diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize.stdout b/src/tools/miri/priroda/tests/ui/dap_initialize.stdout index 4773b876a1df8..595a84f405b40 100644 --- a/src/tools/miri/priroda/tests/ui/dap_initialize.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_initialize.stdout @@ -1,3 +1,5 @@ Content-Length: 143 -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null} \ No newline at end of file +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 + +{"seq":2,"type":"event","event":"initialized"} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize_launch.rs b/src/tools/miri/priroda/tests/ui/dap_initialize_launch.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_initialize_launch.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdin b/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdin new file mode 100644 index 0000000000000..ae4ee94ca0e98 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdin @@ -0,0 +1,5 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdout b/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdout new file mode 100644 index 0000000000000..1bf97320e659e --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdout @@ -0,0 +1,7 @@ +Content-Length: 143 + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 + +{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null} \ No newline at end of file From 5e8f7b9de1f322f27cfe9af1da8519ebdd2f1e5b Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sat, 1 Aug 2026 02:51:49 +0300 Subject: [PATCH 024/100] [Priroda] Handle DAP configurationDone startup request --- src/tools/miri/priroda/src/frontend/dap.rs | 7 +++++++ .../tests/ui/dap_initialize_launch_configuration_done.rs | 3 +++ .../ui/dap_initialize_launch_configuration_done.stdin | 7 +++++++ .../ui/dap_initialize_launch_configuration_done.stdout | 9 +++++++++ 4 files changed, 26 insertions(+) create mode 100644 src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.rs create mode 100644 src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdin create mode 100644 src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index 9d6d77f2a8d44..c2a63ed4e9be6 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -69,6 +69,8 @@ impl DapSession { Command::Initialize(_) => self.handle_initialize(request).map(|()| DispatchOutcome::Continue), Command::Launch(_) => self.handle_launch(request).map(|()| DispatchOutcome::Continue), + Command::ConfigurationDone => + self.handle_configuration_done(request).map(|()| DispatchOutcome::Continue), _ => self.handle_unsupported_request(request).map(|()| DispatchOutcome::Exit), } } @@ -79,6 +81,11 @@ impl DapSession { self.server.respond(response) } + fn handle_configuration_done(&mut self, request: Request) -> ServerResult { + let response = request.success(ResponseBody::ConfigurationDone); + self.server.respond(response) + } + /// FIXME: grow capabilities as Priroda adds DAP features. fn handle_initialize(&mut self, request: Request) -> ServerResult { // Advertise configurationDone support ahead of its handler so VS Code diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.rs b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdin b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdin new file mode 100644 index 0000000000000..106ce5dac35e0 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdin @@ -0,0 +1,7 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}}Content-Length: 56 + +{"seq":3,"type":"request","command":"configurationDone"} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout new file mode 100644 index 0000000000000..d4b48cf24093d --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout @@ -0,0 +1,9 @@ +Content-Length: 143 + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 + +{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 101 + +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null} \ No newline at end of file From 8cb2e7cfb18a4530dbbe84a60239939266a6a7ca Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sat, 1 Aug 2026 02:58:49 +0300 Subject: [PATCH 025/100] [Priroda] Handle DAP threads request --- src/tools/miri/priroda/src/frontend/dap.rs | 14 +++++++++++++- src/tools/miri/priroda/tests/ui/dap_threads.rs | 3 +++ src/tools/miri/priroda/tests/ui/dap_threads.stdin | 9 +++++++++ src/tools/miri/priroda/tests/ui/dap_threads.stdout | 11 +++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/tools/miri/priroda/tests/ui/dap_threads.rs create mode 100644 src/tools/miri/priroda/tests/ui/dap_threads.stdin create mode 100644 src/tools/miri/priroda/tests/ui/dap_threads.stdout diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index c2a63ed4e9be6..4425662116fce 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -1,11 +1,13 @@ use std::io::{self, BufReader, BufWriter}; -use emmy_dap_types::prelude::types::Capabilities; +use emmy_dap_types::prelude::responses::ThreadsResponse; +use emmy_dap_types::prelude::types::{Capabilities, Thread}; use emmy_dap_types::prelude::{Command, Event, Request, ResponseBody, Server}; use miri::{InterpResult, interp_ok}; use crate::debugger::PrirodaContext; +const THREAD_ID: i64 = 1; const MAX_REQUEST_COUNT: usize = 128; type ServerResult = Result; @@ -71,6 +73,7 @@ impl DapSession { Command::Launch(_) => self.handle_launch(request).map(|()| DispatchOutcome::Continue), Command::ConfigurationDone => self.handle_configuration_done(request).map(|()| DispatchOutcome::Continue), + Command::Threads => self.handle_threads(request).map(|()| DispatchOutcome::Continue), _ => self.handle_unsupported_request(request).map(|()| DispatchOutcome::Exit), } } @@ -86,6 +89,15 @@ impl DapSession { self.server.respond(response) } + /// FIXME: replace this with Miri thread state once Priroda exposes a + /// frontend-facing thread model. + fn handle_threads(&mut self, request: Request) -> ServerResult { + let response = request.success(ResponseBody::Threads(ThreadsResponse { + threads: vec![Thread { id: THREAD_ID, name: "main".to_string() }], + })); + self.server.respond(response) + } + /// FIXME: grow capabilities as Priroda adds DAP features. fn handle_initialize(&mut self, request: Request) -> ServerResult { // Advertise configurationDone support ahead of its handler so VS Code diff --git a/src/tools/miri/priroda/tests/ui/dap_threads.rs b/src/tools/miri/priroda/tests/ui/dap_threads.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_threads.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_threads.stdin b/src/tools/miri/priroda/tests/ui/dap_threads.stdin new file mode 100644 index 0000000000000..a17c6406c9c73 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_threads.stdin @@ -0,0 +1,9 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}}Content-Length: 56 + +{"seq":3,"type":"request","command":"configurationDone"}Content-Length: 46 + +{"seq":4,"type":"request","command":"threads"} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_threads.stdout b/src/tools/miri/priroda/tests/ui/dap_threads.stdout new file mode 100644 index 0000000000000..14407c49aca87 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_threads.stdout @@ -0,0 +1,11 @@ +Content-Length: 143 + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 + +{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 101 + +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 135 + +{"seq":5,"type":"response","request_seq":4,"success":true,"command":"threads","body":{"threads":[{"id":1,"name":"main"}]},"error":null} \ No newline at end of file From 5a29686e75616d1db4d5444214aa7c89ae9d5769 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sat, 1 Aug 2026 03:03:24 +0300 Subject: [PATCH 026/100] [Priroda] Handle DAP stackTrace request --- src/tools/miri/priroda/src/frontend/dap.rs | 13 ++++++++++++- src/tools/miri/priroda/tests/ui/dap_stack_trace.rs | 3 +++ .../miri/priroda/tests/ui/dap_stack_trace.stdin | 11 +++++++++++ .../miri/priroda/tests/ui/dap_stack_trace.stdout | 13 +++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/tools/miri/priroda/tests/ui/dap_stack_trace.rs create mode 100644 src/tools/miri/priroda/tests/ui/dap_stack_trace.stdin create mode 100644 src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index 4425662116fce..785601e09df91 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -1,6 +1,6 @@ use std::io::{self, BufReader, BufWriter}; -use emmy_dap_types::prelude::responses::ThreadsResponse; +use emmy_dap_types::prelude::responses::{StackTraceResponse, ThreadsResponse}; use emmy_dap_types::prelude::types::{Capabilities, Thread}; use emmy_dap_types::prelude::{Command, Event, Request, ResponseBody, Server}; use miri::{InterpResult, interp_ok}; @@ -74,6 +74,8 @@ impl DapSession { Command::ConfigurationDone => self.handle_configuration_done(request).map(|()| DispatchOutcome::Continue), Command::Threads => self.handle_threads(request).map(|()| DispatchOutcome::Continue), + Command::StackTrace(_) => + self.handle_stack_trace(request).map(|()| DispatchOutcome::Continue), _ => self.handle_unsupported_request(request).map(|()| DispatchOutcome::Exit), } } @@ -98,6 +100,15 @@ impl DapSession { self.server.respond(response) } + /// FIXME: report real frames once Priroda exposes a frontend-facing stack model. + fn handle_stack_trace(&mut self, request: Request) -> ServerResult { + let response = request.success(ResponseBody::StackTrace(StackTraceResponse { + stack_frames: Vec::new(), + total_frames: Some(0), + })); + self.server.respond(response) + } + /// FIXME: grow capabilities as Priroda adds DAP features. fn handle_initialize(&mut self, request: Request) -> ServerResult { // Advertise configurationDone support ahead of its handler so VS Code diff --git a/src/tools/miri/priroda/tests/ui/dap_stack_trace.rs b/src/tools/miri/priroda/tests/ui/dap_stack_trace.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_stack_trace.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdin b/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdin new file mode 100644 index 0000000000000..1056beef5712e --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdin @@ -0,0 +1,11 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}}Content-Length: 56 + +{"seq":3,"type":"request","command":"configurationDone"}Content-Length: 46 + +{"seq":4,"type":"request","command":"threads"}Content-Length: 76 + +{"seq":5,"type":"request","command":"stackTrace","arguments":{"threadId":1}} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout b/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout new file mode 100644 index 0000000000000..4229c6cd473aa --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout @@ -0,0 +1,13 @@ +Content-Length: 143 + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 + +{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 101 + +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 135 + +{"seq":5,"type":"response","request_seq":4,"success":true,"command":"threads","body":{"threads":[{"id":1,"name":"main"}]},"error":null}Content-Length: 136 + +{"seq":6,"type":"response","request_seq":5,"success":true,"command":"stackTrace","body":{"stackFrames":[],"totalFrames":0},"error":null} \ No newline at end of file From 2d7ddb651cae31dbbc5cefbb269650a5fea21b4e Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sat, 1 Aug 2026 18:26:14 +0300 Subject: [PATCH 027/100] [Priroda] Add core debugger stop-at-first-user-location Add FirstUserSourceLocation ResumeMode variant that stops when the interpreter reaches a user-relevant frame with a source location. This gives the DAP frontend an entry-stop primitive that skips Miri-internal and std frames. --- src/tools/miri/priroda/src/debugger.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/tools/miri/priroda/src/debugger.rs b/src/tools/miri/priroda/src/debugger.rs index aa4cb45b85aba..ee570cffa8f9e 100644 --- a/src/tools/miri/priroda/src/debugger.rs +++ b/src/tools/miri/priroda/src/debugger.rs @@ -100,6 +100,8 @@ enum ResumeMode { /// /// Take `Option` because some cases current state has no mapped to source code location SourceLine(Option<(PathBuf, usize)>), + /// Stop at the first mapped source location from a user-relevant frame. + FirstUserSourceLocation, /// Continue until reaching a breakpoint. Continue, } @@ -152,6 +154,10 @@ impl<'tcx> PrirodaContext<'tcx> { self.resume(ResumeMode::SourceLine(self.current_source_position())) } + pub(super) fn stop_at_first_user_location(&mut self) -> InterpResult<'tcx, StepResult> { + self.resume(ResumeMode::FirstUserSourceLocation) + } + /// Continue execution until reaching a breakpoint or propagating termination. fn continue_execution(&mut self) -> InterpResult<'tcx, StepResult> { self.resume(ResumeMode::Continue) @@ -210,11 +216,26 @@ impl<'tcx> PrirodaContext<'tcx> { } } - ResumeMode::MirInstruction | ResumeMode::Continue => {} + ResumeMode::FirstUserSourceLocation + if self.current_location.is_some() && self.has_user_relevant_frame() => + { + return interp_ok(StepResult::Step); + } + + ResumeMode::MirInstruction + | ResumeMode::FirstUserSourceLocation + | ResumeMode::Continue => {} } } } + fn has_user_relevant_frame(&self) -> bool { + // Walk the whole stack, not just the top frame: during interpreter + // startup the user's `main` can sit under Miri-internal frames that + // have no source span, so checking only `last()` would miss it. + self.ecx.active_thread_stack().iter().any(|frame| frame.extra.user_relevance == u8::MAX) + } + /// Advance Miri by one interpreter-loop transition. fn advance(&mut self) -> InterpResult<'tcx> { // FIXME: use a Miri-owned scheduler-aware debugger step API before From dc713762c74f73e17caf2f42587b34aaddd98be6 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sat, 1 Aug 2026 18:26:14 +0300 Subject: [PATCH 028/100] [Priroda] Wire DAP to interpreter lifecycle and stopped event --- src/tools/miri/priroda/src/frontend/dap.rs | 91 ++++++++++++++----- ...nitialize_launch_configuration_done.stdout | 4 +- .../priroda/tests/ui/dap_stack_trace.stdout | 8 +- .../miri/priroda/tests/ui/dap_threads.stdout | 6 +- 4 files changed, 82 insertions(+), 27 deletions(-) diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index 785601e09df91..600cdf764cdc3 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -1,7 +1,8 @@ use std::io::{self, BufReader, BufWriter}; +use emmy_dap_types::prelude::events::StoppedEventBody; use emmy_dap_types::prelude::responses::{StackTraceResponse, ThreadsResponse}; -use emmy_dap_types::prelude::types::{Capabilities, Thread}; +use emmy_dap_types::prelude::types::{Capabilities, StoppedEventReason, Thread}; use emmy_dap_types::prelude::{Command, Event, Request, ResponseBody, Server}; use miri::{InterpResult, interp_ok}; @@ -23,10 +24,10 @@ impl Dap { /// Serve DAP requests on stdin/stdout. pub(crate) fn run_dap_loop<'tcx>( &self, - _session: &mut PrirodaContext<'tcx>, + session: &mut PrirodaContext<'tcx>, ) -> InterpResult<'tcx> { // FIXME: make this unbounded once Priroda has a full session lifecycle. - if let Err(err) = DapSession::stdio().run_requests() { + if let Err(err) = DapSession::stdio().run_requests(session)? { eprintln!("priroda dap error: {err}"); } @@ -39,6 +40,7 @@ type DapServer = Server, io::StdoutLock<'static>>; /// Owns the DAP stdio transport and dispatches requests into Priroda handlers. struct DapSession { server: DapServer, + initialized: bool, } impl DapSession { @@ -48,35 +50,59 @@ impl DapSession { BufReader::new(io::stdin().lock()), BufWriter::new(io::stdout().lock()), ), + initialized: false, } } - fn run_requests(&mut self) -> ServerResult { + fn run_requests<'tcx>( + &mut self, + session: &mut PrirodaContext<'tcx>, + ) -> InterpResult<'tcx, ServerResult> { for _ in 0..MAX_REQUEST_COUNT { - let Some(request) = self.server.poll_request()? else { - return Ok(()); + let request = match self.server.poll_request() { + Ok(Some(request)) => request, + Ok(None) => return interp_ok(Ok(())), + Err(err) => return interp_ok(Err(err)), }; - match self.dispatch_request(request)? { - DispatchOutcome::Continue => {} - DispatchOutcome::Exit => return Ok(()), + match self.dispatch_request(request, session)? { + Ok(DispatchOutcome::Continue) => {} + Ok(DispatchOutcome::Exit) => return interp_ok(Ok(())), + Err(err) => return interp_ok(Err(err)), } } - Ok(()) + interp_ok(Ok(())) } - fn dispatch_request(&mut self, request: Request) -> ServerResult { + fn dispatch_request<'tcx>( + &mut self, + request: Request, + session: &mut PrirodaContext<'tcx>, + ) -> InterpResult<'tcx, ServerResult> { + // Reject non-initialize requests before the handshake completes so the + // client gets a framed error. + if !self.initialized && !matches!(&request.command, Command::Initialize(_)) { + return interp_ok( + self.handle_unsupported_request(request).map(|()| DispatchOutcome::Exit), + ); + } + match &request.command { Command::Initialize(_) => - self.handle_initialize(request).map(|()| DispatchOutcome::Continue), - Command::Launch(_) => self.handle_launch(request).map(|()| DispatchOutcome::Continue), - Command::ConfigurationDone => - self.handle_configuration_done(request).map(|()| DispatchOutcome::Continue), - Command::Threads => self.handle_threads(request).map(|()| DispatchOutcome::Continue), + interp_ok(self.handle_initialize(request).map(|()| DispatchOutcome::Continue)), + Command::Launch(_) => + interp_ok(self.handle_launch(request).map(|()| DispatchOutcome::Continue)), + Command::ConfigurationDone => { + let res = self.handle_configuration_done(request, session)?; + interp_ok(res.map(|()| DispatchOutcome::Continue)) + } + Command::Threads => + interp_ok(self.handle_threads(request).map(|()| DispatchOutcome::Continue)), Command::StackTrace(_) => - self.handle_stack_trace(request).map(|()| DispatchOutcome::Continue), - _ => self.handle_unsupported_request(request).map(|()| DispatchOutcome::Exit), + interp_ok(self.handle_stack_trace(request).map(|()| DispatchOutcome::Continue)), + _ => + interp_ok(self.handle_unsupported_request(request).map(|()| DispatchOutcome::Exit)), } } @@ -86,9 +112,18 @@ impl DapSession { self.server.respond(response) } - fn handle_configuration_done(&mut self, request: Request) -> ServerResult { + fn handle_configuration_done<'tcx>( + &mut self, + request: Request, + session: &mut PrirodaContext<'tcx>, + ) -> InterpResult<'tcx, ServerResult> { + session.stop_at_first_user_location()?; let response = request.success(ResponseBody::ConfigurationDone); - self.server.respond(response) + interp_ok( + self.server + .respond(response) + .and_then(|()| self.send_stopped_event(StoppedEventReason::Entry)), + ) } /// FIXME: replace this with Miri thread state once Priroda exposes a @@ -118,7 +153,9 @@ impl DapSession { ..Capabilities::default() })); self.server.respond(response)?; - self.server.send_event(Event::Initialized) + self.server.send_event(Event::Initialized)?; + self.initialized = true; + Ok(()) } fn handle_unsupported_request(&mut self, request: Request) -> ServerResult { @@ -130,6 +167,18 @@ impl DapSession { self.server.respond(response) } + fn send_stopped_event(&mut self, reason: StoppedEventReason) -> ServerResult { + self.server.send_event(Event::Stopped(StoppedEventBody { + reason, + description: None, + thread_id: Some(THREAD_ID), + preserve_focus_hint: None, + text: None, + all_threads_stopped: Some(true), + hit_breakpoint_ids: None, + })) + } + fn display_command(command: &Command) -> &'static str { match command { Command::Initialize(_) => "initialize", diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout index d4b48cf24093d..af8fbfd94f4fa 100644 --- a/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout @@ -6,4 +6,6 @@ Content-Length: 143 {"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 101 -{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null} \ No newline at end of file +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 186 + +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout b/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout index 4229c6cd473aa..88cf8f01933db 100644 --- a/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout @@ -6,8 +6,10 @@ Content-Length: 143 {"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 101 -{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 135 +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 186 -{"seq":5,"type":"response","request_seq":4,"success":true,"command":"threads","body":{"threads":[{"id":1,"name":"main"}]},"error":null}Content-Length: 136 +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 135 -{"seq":6,"type":"response","request_seq":5,"success":true,"command":"stackTrace","body":{"stackFrames":[],"totalFrames":0},"error":null} \ No newline at end of file +{"seq":6,"type":"response","request_seq":4,"success":true,"command":"threads","body":{"threads":[{"id":1,"name":"main"}]},"error":null}Content-Length: 136 + +{"seq":7,"type":"response","request_seq":5,"success":true,"command":"stackTrace","body":{"stackFrames":[],"totalFrames":0},"error":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_threads.stdout b/src/tools/miri/priroda/tests/ui/dap_threads.stdout index 14407c49aca87..953d114e22ec2 100644 --- a/src/tools/miri/priroda/tests/ui/dap_threads.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_threads.stdout @@ -6,6 +6,8 @@ Content-Length: 143 {"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 101 -{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 135 +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 186 -{"seq":5,"type":"response","request_seq":4,"success":true,"command":"threads","body":{"threads":[{"id":1,"name":"main"}]},"error":null} \ No newline at end of file +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 135 + +{"seq":6,"type":"response","request_seq":4,"success":true,"command":"threads","body":{"threads":[{"id":1,"name":"main"}]},"error":null} \ No newline at end of file From 84ec755e41f2cb2d27fe6912799d992cdd21f51e Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sat, 1 Aug 2026 18:27:15 +0300 Subject: [PATCH 029/100] [Priroda] Report current DAP stack frame --- src/tools/miri/priroda/src/debugger.rs | 8 ++- src/tools/miri/priroda/src/frontend/dap.rs | 60 ++++++++++++++++--- .../priroda/tests/ui/dap_stack_trace.stdout | 4 +- 3 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/tools/miri/priroda/src/debugger.rs b/src/tools/miri/priroda/src/debugger.rs index ee570cffa8f9e..cddd55b77ba68 100644 --- a/src/tools/miri/priroda/src/debugger.rs +++ b/src/tools/miri/priroda/src/debugger.rs @@ -17,6 +17,7 @@ pub(super) struct SourceLocation { // storing `span` to use it lazily to compute path. pub(super) span: Span, pub(super) line: usize, + pub(super) column: usize, } impl SourceLocation { @@ -158,6 +159,11 @@ impl<'tcx> PrirodaContext<'tcx> { self.resume(ResumeMode::FirstUserSourceLocation) } + pub(super) fn current_frame_name(&self) -> Option { + let frame = self.ecx.active_thread_stack().last()?; + Some(frame.instance().to_string()) + } + /// Continue execution until reaching a breakpoint or propagating termination. fn continue_execution(&mut self) -> InterpResult<'tcx, StepResult> { self.resume(ResumeMode::Continue) @@ -304,7 +310,7 @@ impl<'tcx> PrirodaContext<'tcx> { let source_map = self.ecx.tcx.sess.source_map(); let loc = source_map.lookup_char_pos(span.lo()); - Some(SourceLocation { span, line: loc.line }) + Some(SourceLocation { span, line: loc.line, column: loc.col_display + 1 }) } pub(super) fn run_command( diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index 600cdf764cdc3..3b6cdb01fc981 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -2,13 +2,16 @@ use std::io::{self, BufReader, BufWriter}; use emmy_dap_types::prelude::events::StoppedEventBody; use emmy_dap_types::prelude::responses::{StackTraceResponse, ThreadsResponse}; -use emmy_dap_types::prelude::types::{Capabilities, StoppedEventReason, Thread}; +use emmy_dap_types::prelude::types::{ + Capabilities, Source, StackFrame, StoppedEventReason, Thread, +}; use emmy_dap_types::prelude::{Command, Event, Request, ResponseBody, Server}; -use miri::{InterpResult, interp_ok}; +use miri::{InterpResult, bug, interp_ok}; use crate::debugger::PrirodaContext; const THREAD_ID: i64 = 1; +const STACK_FRAME_ID: i64 = 1; const MAX_REQUEST_COUNT: usize = 128; type ServerResult = Result; @@ -100,7 +103,9 @@ impl DapSession { Command::Threads => interp_ok(self.handle_threads(request).map(|()| DispatchOutcome::Continue)), Command::StackTrace(_) => - interp_ok(self.handle_stack_trace(request).map(|()| DispatchOutcome::Continue)), + interp_ok( + self.handle_stack_trace(request, session).map(|()| DispatchOutcome::Continue), + ), _ => interp_ok(self.handle_unsupported_request(request).map(|()| DispatchOutcome::Exit)), } @@ -135,11 +140,52 @@ impl DapSession { self.server.respond(response) } - /// FIXME: report real frames once Priroda exposes a frontend-facing stack model. - fn handle_stack_trace(&mut self, request: Request) -> ServerResult { + /// FIXME: report all frames once Priroda exposes a frontend-facing stack model. + fn handle_stack_trace<'tcx>( + &mut self, + request: Request, + session: &PrirodaContext<'tcx>, + ) -> ServerResult { + let stack_frames = match &session.current_location { + Some(location) => { + let path = session.local_path(location); + vec![StackFrame { + id: STACK_FRAME_ID, + name: session.current_frame_name().unwrap_or_else(|| "".to_string()), + source: path.as_ref().map(|path| { + Source { + name: path.file_name().map(|name| name.to_string_lossy().into_owned()), + path: Some(path.display().to_string()), + source_reference: None, + presentation_hint: None, + origin: None, + sources: None, + checksums: None, + } + }), + line: location + .line + .try_into() + .unwrap_or_else(|_| bug!("source line exceeds i64")), + column: location + .column + .try_into() + .unwrap_or_else(|_| bug!("source column exceeds i64")), + end_line: None, + end_column: None, + can_restart: None, + instruction_pointer_reference: None, + module_id: None, + presentation_hint: None, + }] + } + None => Vec::new(), + }; + let total_frames: i64 = + stack_frames.len().try_into().unwrap_or_else(|_| bug!("frame count exceeds i64")); let response = request.success(ResponseBody::StackTrace(StackTraceResponse { - stack_frames: Vec::new(), - total_frames: Some(0), + stack_frames, + total_frames: Some(total_frames), })); self.server.respond(response) } diff --git a/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout b/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout index 88cf8f01933db..318eefe286bac 100644 --- a/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout @@ -10,6 +10,6 @@ Content-Length: 143 {"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 135 -{"seq":6,"type":"response","request_seq":4,"success":true,"command":"threads","body":{"threads":[{"id":1,"name":"main"}]},"error":null}Content-Length: 136 +{"seq":6,"type":"response","request_seq":4,"success":true,"command":"threads","body":{"threads":[{"id":1,"name":"main"}]},"error":null}Content-Length: 295 -{"seq":7,"type":"response","request_seq":5,"success":true,"command":"stackTrace","body":{"stackFrames":[],"totalFrames":0},"error":null} \ No newline at end of file +{"seq":7,"type":"response","request_seq":5,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_stack_trace.rs","path":"{MANIFEST_DIR}/tests/ui/dap_stack_trace.rs"},"line":3,"column":11}],"totalFrames":1},"error":null} \ No newline at end of file From abef4585be35f2cade9e7fc3daf208dca46fa2cc Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sat, 1 Aug 2026 18:27:50 +0300 Subject: [PATCH 030/100] [Priroda] Add DAP locals scope and variables --- src/tools/miri/priroda/src/debugger.rs | 2 +- src/tools/miri/priroda/src/frontend/dap.rs | 86 ++++++++++++++++++- .../priroda/tests/ui/dap_scopes_variables.rs | 7 ++ .../tests/ui/dap_scopes_variables.stdin | 13 +++ .../tests/ui/dap_scopes_variables.stdout | 17 ++++ 5 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 src/tools/miri/priroda/tests/ui/dap_scopes_variables.rs create mode 100644 src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdin create mode 100644 src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout diff --git a/src/tools/miri/priroda/src/debugger.rs b/src/tools/miri/priroda/src/debugger.rs index cddd55b77ba68..b40ae12cdc396 100644 --- a/src/tools/miri/priroda/src/debugger.rs +++ b/src/tools/miri/priroda/src/debugger.rs @@ -356,7 +356,7 @@ impl<'tcx> PrirodaContext<'tcx> { /// /// Starts from all MIR locals, then enriches them with source names from /// `var_debug_info` when a debug entry maps directly to a whole local. - fn list_locals(&self) -> Vec { + pub(super) fn list_locals(&self) -> Vec { let Some(frame) = self.ecx.active_thread_stack().last() else { return Vec::new(); }; diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index 3b6cdb01fc981..01c3fbd2d5019 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -1,17 +1,21 @@ use std::io::{self, BufReader, BufWriter}; use emmy_dap_types::prelude::events::StoppedEventBody; -use emmy_dap_types::prelude::responses::{StackTraceResponse, ThreadsResponse}; +use emmy_dap_types::prelude::responses::{ + ScopesResponse, StackTraceResponse, ThreadsResponse, VariablesResponse, +}; use emmy_dap_types::prelude::types::{ - Capabilities, Source, StackFrame, StoppedEventReason, Thread, + Capabilities, Scope, ScopePresentationhint, Source, StackFrame, StoppedEventReason, Thread, + Variable, }; use emmy_dap_types::prelude::{Command, Event, Request, ResponseBody, Server}; use miri::{InterpResult, bug, interp_ok}; -use crate::debugger::PrirodaContext; +use crate::debugger::{LocalDesc, PrirodaContext}; const THREAD_ID: i64 = 1; const STACK_FRAME_ID: i64 = 1; +const LOCALS_VARIABLES_REFERENCE: i64 = 1; const MAX_REQUEST_COUNT: usize = 128; type ServerResult = Result; @@ -106,6 +110,12 @@ impl DapSession { interp_ok( self.handle_stack_trace(request, session).map(|()| DispatchOutcome::Continue), ), + Command::Scopes(_) => + interp_ok(self.handle_scopes(request, session).map(|()| DispatchOutcome::Continue)), + Command::Variables(_) => + interp_ok( + self.handle_variables(request, session).map(|()| DispatchOutcome::Continue), + ), _ => interp_ok(self.handle_unsupported_request(request).map(|()| DispatchOutcome::Exit)), } @@ -117,6 +127,45 @@ impl DapSession { self.server.respond(response) } + fn handle_scopes<'tcx>( + &mut self, + request: Request, + _session: &PrirodaContext<'tcx>, + ) -> ServerResult { + let response = request.success(ResponseBody::Scopes(ScopesResponse { + scopes: vec![Scope { + name: "Locals".to_string(), + presentation_hint: Some(ScopePresentationhint::Locals), + variables_reference: LOCALS_VARIABLES_REFERENCE, + named_variables: None, + indexed_variables: Some(0), + expensive: false, + source: None, + line: None, + column: None, + end_line: None, + end_column: None, + }], + })); + self.server.respond(response) + } + + fn handle_variables<'tcx>( + &mut self, + request: Request, + session: &PrirodaContext<'tcx>, + ) -> ServerResult { + let variables = match &request.command { + Command::Variables(args) if args.variables_reference == LOCALS_VARIABLES_REFERENCE => + session.list_locals().into_iter().map(Self::local_to_variable).collect(), + Command::Variables(_) => Vec::new(), + _ => unreachable!(), + }; + + let response = request.success(ResponseBody::Variables(VariablesResponse { variables })); + self.server.respond(response) + } + fn handle_configuration_done<'tcx>( &mut self, request: Request, @@ -240,4 +289,35 @@ impl DapSession { _ => "unsupported", } } + + fn local_to_variable(local: LocalDesc) -> Variable { + Variable { + name: Self::local_name(&local), + value: local.value, + type_field: Some(local.ty), + presentation_hint: None, + evaluate_name: None, + // FIXME: add child handles once Priroda can identify places across requests. + variables_reference: 0, + named_variables: None, + indexed_variables: None, + memory_reference: None, + } + } + + fn local_name(local: &LocalDesc) -> String { + let source_projection = local.source_projection_str(); + + // Prefer source names when debug info gives us one. If a local only has + // MIR storage identity, keep that visible so the DAP Variables view + // still has a stable row for every backing local. + if let Some(source_name) = local.source_name { + return format!("{source_name}{source_projection}"); + } + + let local_id = local + .local + .map_or_else(|| "".to_string(), |local_idx| format!("_{}", local_idx.index())); + format!("{local_id}{}", local.storage_projection_str()) + } } diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables.rs b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.rs new file mode 100644 index 0000000000000..081c3ce1d97c6 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.rs @@ -0,0 +1,7 @@ +//@ compile-flags: --dap + +fn main() { + let x = 1_i32; + let y = true; + let _ = (x, y); +} diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdin b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdin new file mode 100644 index 0000000000000..d1dd783eb96fa --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdin @@ -0,0 +1,13 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}}Content-Length: 56 + +{"seq":3,"type":"request","command":"configurationDone"}Content-Length: 76 + +{"seq":4,"type":"request","command":"stackTrace","arguments":{"threadId":1}}Content-Length: 71 + +{"seq":5,"type":"request","command":"scopes","arguments":{"frameId":1}}Content-Length: 85 + +{"seq":6,"type":"request","command":"variables","arguments":{"variablesReference":1}} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout new file mode 100644 index 0000000000000..d7a47ffb2d178 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout @@ -0,0 +1,17 @@ +Content-Length: 143 + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 + +{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 101 + +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 186 + +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 304 + +{"seq":6,"type":"response","request_seq":4,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables.rs"},"line":4,"column":9}],"totalFrames":1},"error":null}Content-Length: 218 + +{"seq":7,"type":"response","request_seq":5,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false}]},"error":null}Content-Length: 527 + +{"seq":8,"type":"response","request_seq":6,"success":true,"command":"variables","body":{"variables":[{"name":"_0","value":"","type":"()","variablesReference":0},{"name":"x","value":"","type":"i32","variablesReference":0},{"name":"y","value":"","type":"bool","variablesReference":0},{"name":"_3","value":"","type":"(i32, bool)","variablesReference":0},{"name":"_4","value":"","type":"i32","variablesReference":0},{"name":"_5","value":"","type":"bool","variablesReference":0}]},"error":null} \ No newline at end of file From d233df9d66cda4273dc32e95f473f58e807c5c93 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 2 Aug 2026 19:33:55 +0300 Subject: [PATCH 031/100] [Priroda] Add bounded DAP source-line stepping demo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire `next` and `stepIn` DAP requests to Priroda's existing source-line step. Both commands use the same `handle_step` handler for now; true step-over vs step-in semantics are deferred. Add `stopped_reason` to map `StepResult` variants to DAP `StoppedEventReason` so the editor can distinguish a manual step from a breakpoint hit. Add a `handle_disconnect` handler that sends the `terminated` event and exits the session cleanly. Document the `SourceLocation` span-storage rationale and refine the `SourceLine` resume-mode comment to be clearer about the "no source location → first mapped location" semantics. --- src/tools/miri/priroda/src/debugger.rs | 22 ++++++--- src/tools/miri/priroda/src/frontend/dap.rs | 46 ++++++++++++++++++- .../tests/ui/dap_scopes_variables_next.rs | 7 +++ .../tests/ui/dap_scopes_variables_next.stdin | 23 ++++++++++ .../tests/ui/dap_scopes_variables_next.stdout | 31 +++++++++++++ 5 files changed, 121 insertions(+), 8 deletions(-) create mode 100644 src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.rs create mode 100644 src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdin create mode 100644 src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout diff --git a/src/tools/miri/priroda/src/debugger.rs b/src/tools/miri/priroda/src/debugger.rs index b40ae12cdc396..557af87156b5f 100644 --- a/src/tools/miri/priroda/src/debugger.rs +++ b/src/tools/miri/priroda/src/debugger.rs @@ -14,7 +14,8 @@ use rustc_span::{Span, Symbol}; /// Structured source information for frontends. pub(super) struct SourceLocation { - // storing `span` to use it lazily to compute path. + // Keep the span so each frontend can resolve paths with its own rendering + // rules instead of forcing every caller to use one path representation. pub(super) span: Span, pub(super) line: usize, pub(super) column: usize, @@ -97,11 +98,15 @@ impl LocalDesc { enum ResumeMode { /// Stop at the next visible MIR instruction. MirInstruction, - /// Stop at the next source line + /// Stop at the next source line. /// - /// Take `Option` because some cases current state has no mapped to source code location + /// `None` means the current interpreter position has no source location, so + /// the first mapped source location is good enough to report. SourceLine(Option<(PathBuf, usize)>), /// Stop at the first mapped source location from a user-relevant frame. + /// + /// This is the DAP entry-stop primitive: it skips over interpreter startup + /// and Miri-internal frames until there is a location an editor can show. FirstUserSourceLocation, /// Continue until reaching a breakpoint. Continue, @@ -151,14 +156,17 @@ impl<'tcx> PrirodaContext<'tcx> { fn stepi(&mut self) -> InterpResult<'tcx, StepResult> { self.resume(ResumeMode::MirInstruction) } - fn step(&mut self) -> InterpResult<'tcx, StepResult> { + /// Step until the displayed source file or line changes. + pub(super) fn step(&mut self) -> InterpResult<'tcx, StepResult> { self.resume(ResumeMode::SourceLine(self.current_source_position())) } + /// Run until the initial editor-visible stop point. pub(super) fn stop_at_first_user_location(&mut self) -> InterpResult<'tcx, StepResult> { self.resume(ResumeMode::FirstUserSourceLocation) } + /// Return the active frame name while DAP still reports only one frame. pub(super) fn current_frame_name(&self) -> Option { let frame = self.ecx.active_thread_stack().last()?; Some(frame.instance().to_string()) @@ -205,12 +213,14 @@ impl<'tcx> PrirodaContext<'tcx> { ResumeMode::SourceLine(ref prev_location) => { match (prev_location, &self.current_location) { - // We started from an unmapped source location. Stop at the first mapped source location we can show to the user. + // We started from an unmapped location; stop once there + // is a source position the frontend can display. (None, Some(_)) => return interp_ok(StepResult::Step), (Some((prev_path, prev_line)), Some(current_location)) => { if let Some(current_path) = self.local_path(current_location) { - // A source step stops when the visible source position changes to a different file or line. + // A source step stops when the displayed source + // position changes to a different file or line. if *prev_path != current_path || *prev_line != current_location.line { return interp_ok(StepResult::Step); diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index 01c3fbd2d5019..38906e0969f28 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -11,8 +11,10 @@ use emmy_dap_types::prelude::types::{ use emmy_dap_types::prelude::{Command, Event, Request, ResponseBody, Server}; use miri::{InterpResult, bug, interp_ok}; -use crate::debugger::{LocalDesc, PrirodaContext}; +use crate::debugger::{LocalDesc, PrirodaContext, StepResult}; +// Priroda still exposes one interpreted thread and one selected frame to DAP. +// Keep the ids stable so editor follow-up requests can address the stopped state. const THREAD_ID: i64 = 1; const STACK_FRAME_ID: i64 = 1; const LOCALS_VARIABLES_REFERENCE: i64 = 1; @@ -116,8 +118,21 @@ impl DapSession { interp_ok( self.handle_variables(request, session).map(|()| DispatchOutcome::Continue), ), + Command::Next(_) | Command::StepIn(_) => { + let body = match &request.command { + Command::Next(_) => ResponseBody::Next, + Command::StepIn(_) => ResponseBody::StepIn, + _ => unreachable!(), + }; + let res = self.handle_step(request, body, session)?; + interp_ok(res.map(|()| DispatchOutcome::Continue)) + } + Command::Disconnect(_) => + interp_ok(self.handle_disconnect(request).map(|()| DispatchOutcome::Exit)), _ => - interp_ok(self.handle_unsupported_request(request).map(|()| DispatchOutcome::Exit)), + interp_ok( + self.handle_unsupported_request(request).map(|()| DispatchOutcome::Exit), + ), } } @@ -253,6 +268,26 @@ impl DapSession { Ok(()) } + /// FIXME: distinguish step-over from step-in once Priroda has call-aware stepping. + fn handle_step<'tcx>( + &mut self, + request: Request, + body: ResponseBody, + session: &mut PrirodaContext<'tcx>, + ) -> InterpResult<'tcx, ServerResult> { + let result = session.step()?; + interp_ok( + self.server + .respond(request.success(body)) + .and_then(|()| self.send_stopped_event(Self::stopped_reason(result))), + ) + } + + fn handle_disconnect(&mut self, request: Request) -> ServerResult { + self.server.respond(request.success(ResponseBody::Disconnect))?; + self.server.send_event(Event::Terminated(None)) + } + fn handle_unsupported_request(&mut self, request: Request) -> ServerResult { eprintln!( "priroda dap: unsupported request during DAP demo milestone: {}", @@ -274,6 +309,13 @@ impl DapSession { })) } + fn stopped_reason(result: StepResult) -> StoppedEventReason { + match result { + StepResult::Step => StoppedEventReason::Step, + StepResult::Breakpoint => StoppedEventReason::Breakpoint, + } + } + fn display_command(command: &Command) -> &'static str { match command { Command::Initialize(_) => "initialize", diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.rs b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.rs new file mode 100644 index 0000000000000..081c3ce1d97c6 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.rs @@ -0,0 +1,7 @@ +//@ compile-flags: --dap + +fn main() { + let x = 1_i32; + let y = true; + let _ = (x, y); +} diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdin b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdin new file mode 100644 index 0000000000000..40da18a5832da --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdin @@ -0,0 +1,23 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}}Content-Length: 56 + +{"seq":3,"type":"request","command":"configurationDone"}Content-Length: 76 + +{"seq":4,"type":"request","command":"stackTrace","arguments":{"threadId":1}}Content-Length: 71 + +{"seq":5,"type":"request","command":"scopes","arguments":{"frameId":1}}Content-Length: 85 + +{"seq":6,"type":"request","command":"variables","arguments":{"variablesReference":1}}Content-Length: 70 + +{"seq":7,"type":"request","command":"next","arguments":{"threadId":1}}Content-Length: 76 + +{"seq":8,"type":"request","command":"stackTrace","arguments":{"threadId":1}}Content-Length: 71 + +{"seq":9,"type":"request","command":"scopes","arguments":{"frameId":1}}Content-Length: 86 + +{"seq":10,"type":"request","command":"variables","arguments":{"variablesReference":1}}Content-Length: 65 + +{"seq":11,"type":"request","command":"disconnect","arguments":{}} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout new file mode 100644 index 0000000000000..3620bd1a0b9cb --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout @@ -0,0 +1,31 @@ +Content-Length: 143 + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 + +{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 101 + +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 186 + +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 314 + +{"seq":6,"type":"response","request_seq":4,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs"},"line":4,"column":9}],"totalFrames":1},"error":null}Content-Length: 218 + +{"seq":7,"type":"response","request_seq":5,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false}]},"error":null}Content-Length: 527 + +{"seq":8,"type":"response","request_seq":6,"success":true,"command":"variables","body":{"variables":[{"name":"_0","value":"","type":"()","variablesReference":0},{"name":"x","value":"","type":"i32","variablesReference":0},{"name":"y","value":"","type":"bool","variablesReference":0},{"name":"_3","value":"","type":"(i32, bool)","variablesReference":0},{"name":"_4","value":"","type":"i32","variablesReference":0},{"name":"_5","value":"","type":"bool","variablesReference":0}]},"error":null}Content-Length: 88 + +{"seq":9,"type":"response","request_seq":7,"success":true,"command":"next","error":null}Content-Length: 186 + +{"seq":10,"type":"event","event":"stopped","body":{"reason":"step","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 315 + +{"seq":11,"type":"response","request_seq":8,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs"},"line":5,"column":9}],"totalFrames":1},"error":null}Content-Length: 219 + +{"seq":12,"type":"response","request_seq":9,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false}]},"error":null}Content-Length: 528 + +{"seq":13,"type":"response","request_seq":10,"success":true,"command":"variables","body":{"variables":[{"name":"_0","value":"","type":"()","variablesReference":0},{"name":"x","value":"1_i32","type":"i32","variablesReference":0},{"name":"y","value":"","type":"bool","variablesReference":0},{"name":"_3","value":"","type":"(i32, bool)","variablesReference":0},{"name":"_4","value":"","type":"i32","variablesReference":0},{"name":"_5","value":"","type":"bool","variablesReference":0}]},"error":null}Content-Length: 96 + +{"seq":14,"type":"response","request_seq":11,"success":true,"command":"disconnect","error":null}Content-Length: 58 + +{"seq":15,"type":"event","event":"terminated","body":null} \ No newline at end of file From cb35d76fe95ad4746123e78eecc93cd68576628a Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 2 Aug 2026 19:35:35 +0300 Subject: [PATCH 032/100] [Priroda] Return Continue instead of Exit for unsupported DAP requests When the session receives an unsupported DAP request, return `DispatchOutcome::Continue` instead of `Exit` so the debug adapter keeps running after sending the error response. Remove the `eprintln!` side channel from `handle_unsupported_request` since the framed DAP error response is the single authoritative error-reporting path. Include the command name in the error message string so the DAP client sees which request was rejected. --- src/tools/miri/priroda/src/frontend/dap.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index 38906e0969f28..40012acec64c2 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -93,7 +93,7 @@ impl DapSession { // client gets a framed error. if !self.initialized && !matches!(&request.command, Command::Initialize(_)) { return interp_ok( - self.handle_unsupported_request(request).map(|()| DispatchOutcome::Exit), + self.handle_unsupported_request(request).map(|()| DispatchOutcome::Continue), ); } @@ -131,7 +131,7 @@ impl DapSession { interp_ok(self.handle_disconnect(request).map(|()| DispatchOutcome::Exit)), _ => interp_ok( - self.handle_unsupported_request(request).map(|()| DispatchOutcome::Exit), + self.handle_unsupported_request(request).map(|()| DispatchOutcome::Continue), ), } } @@ -289,11 +289,11 @@ impl DapSession { } fn handle_unsupported_request(&mut self, request: Request) -> ServerResult { - eprintln!( - "priroda dap: unsupported request during DAP demo milestone: {}", + let message = format!( + "unsupported request in Priroda DAP demo mode: {}", Self::display_command(&request.command) ); - let response = request.error("unsupported request in Priroda DAP demo mode"); + let response = request.error(&message); self.server.respond(response) } From f1fb454db5b85ab66ad04f46b8adb0aacbd9724f Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 2 Aug 2026 09:05:41 +0300 Subject: [PATCH 033/100] [Priroda] Document DAP prototype in README --- src/tools/miri/priroda/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/tools/miri/priroda/README.md b/src/tools/miri/priroda/README.md index a8c25bf279868..6283bc28bb7c2 100644 --- a/src/tools/miri/priroda/README.md +++ b/src/tools/miri/priroda/README.md @@ -38,6 +38,18 @@ from `miri/priroda/`: cargo run -- ../tests/pass/empty_main.rs ``` +## DAP Prototype + +Priroda's `--dap` mode speaks a bounded Debug Adapter Protocol prototype over +stdio. It currently supports the startup handshake, stops at the first +user-relevant source location after `configurationDone`, reports one current +stack frame, exposes one flat Locals scope, and maps `list_locals()` into DAP +variables with no child expansion. + +The `next` and `stepIn` requests are wired to Priroda's existing source-line +step so VS Code can drive one visible step. They are not true DAP step-over or +step-in semantics yet. + ## Test Priroda's CLI tests also need `MIRI_SYSROOT`. Run them from `miri/priroda/`: From afd33c80716d1d8422f9f7e6fd3af58fdf49d883 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 2 Aug 2026 07:56:18 +0300 Subject: [PATCH 034/100] [Priroda] Translate interpreter exits into DAP events --- src/tools/miri/priroda/src/frontend/dap.rs | 84 ++++++++++++++++++---- 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index 40012acec64c2..2c4f41e4fb732 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -1,6 +1,6 @@ use std::io::{self, BufReader, BufWriter}; -use emmy_dap_types::prelude::events::StoppedEventBody; +use emmy_dap_types::prelude::events::{ExitedEventBody, StoppedEventBody}; use emmy_dap_types::prelude::responses::{ ScopesResponse, StackTraceResponse, ThreadsResponse, VariablesResponse, }; @@ -9,7 +9,7 @@ use emmy_dap_types::prelude::types::{ Variable, }; use emmy_dap_types::prelude::{Command, Event, Request, ResponseBody, Server}; -use miri::{InterpResult, bug, interp_ok}; +use miri::{InterpErrorInfo, InterpErrorKind, InterpResult, TerminationInfo, bug, interp_ok}; use crate::debugger::{LocalDesc, PrirodaContext, StepResult}; @@ -26,6 +26,12 @@ enum DispatchOutcome { Exit, } +enum ExecutionOutcome { + Stopped(StepResult), + Terminated { code: i32 }, + Failed(String), +} + /// Debug Adapter Protocol frontend. pub(crate) struct Dap; @@ -186,13 +192,20 @@ impl DapSession { request: Request, session: &mut PrirodaContext<'tcx>, ) -> InterpResult<'tcx, ServerResult> { - session.stop_at_first_user_location()?; - let response = request.success(ResponseBody::ConfigurationDone); - interp_ok( - self.server - .respond(response) - .and_then(|()| self.send_stopped_event(StoppedEventReason::Entry)), - ) + match Self::execution_outcome(session.stop_at_first_user_location()) { + ExecutionOutcome::Stopped(_) => { + let response = request.success(ResponseBody::ConfigurationDone); + interp_ok( + self.server + .respond(response) + .and_then(|()| self.send_stopped_event(StoppedEventReason::Entry)), + ) + } + ExecutionOutcome::Terminated { code } => + interp_ok(self.respond_terminated(request, ResponseBody::ConfigurationDone, code)), + ExecutionOutcome::Failed(message) => + interp_ok(self.respond_execution_error(request, message)), + } } /// FIXME: replace this with Miri thread state once Priroda exposes a @@ -275,12 +288,18 @@ impl DapSession { body: ResponseBody, session: &mut PrirodaContext<'tcx>, ) -> InterpResult<'tcx, ServerResult> { - let result = session.step()?; - interp_ok( - self.server - .respond(request.success(body)) - .and_then(|()| self.send_stopped_event(Self::stopped_reason(result))), - ) + match Self::execution_outcome(session.step()) { + ExecutionOutcome::Stopped(result) => + interp_ok( + self.server + .respond(request.success(body)) + .and_then(|()| self.send_stopped_event(Self::stopped_reason(result))), + ), + ExecutionOutcome::Terminated { code } => + interp_ok(self.respond_terminated(request, body, code)), + ExecutionOutcome::Failed(message) => + interp_ok(self.respond_execution_error(request, message)), + } } fn handle_disconnect(&mut self, request: Request) -> ServerResult { @@ -297,6 +316,41 @@ impl DapSession { self.server.respond(response) } + fn respond_execution_error(&mut self, request: Request, message: String) -> ServerResult { + self.server.respond(request.error(&message))?; + self.server.send_event(Event::Terminated(None)) + } + + fn respond_terminated( + &mut self, + request: Request, + body: ResponseBody, + code: i32, + ) -> ServerResult { + self.server.respond(request.success(body))?; + self.server.send_event(Event::Exited(ExitedEventBody { exit_code: code.into() }))?; + self.server.send_event(Event::Terminated(None))?; + Ok(()) + } + + fn execution_outcome<'tcx>(result: InterpResult<'tcx, StepResult>) -> ExecutionOutcome { + match result.report_err() { + Ok(step) => ExecutionOutcome::Stopped(step), + Err(err) => Self::interp_error_outcome(err), + } + } + + fn interp_error_outcome<'tcx>(err: InterpErrorInfo<'tcx>) -> ExecutionOutcome { + let kind = err.into_kind(); + if let InterpErrorKind::MachineStop(info) = &kind + && let Some(TerminationInfo::Exit { code, .. }) = info.downcast_ref::() + { + return ExecutionOutcome::Terminated { code: *code }; + } + + ExecutionOutcome::Failed(kind.to_string()) + } + fn send_stopped_event(&mut self, reason: StoppedEventReason) -> ServerResult { self.server.send_event(Event::Stopped(StoppedEventBody { reason, From dc93370b69d7cabb5d2bc720827ab587b9f6ef7b Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 2 Aug 2026 08:49:41 +0300 Subject: [PATCH 035/100] [Priroda] Track DAP lifecycle state and validate request ids --- src/tools/miri/priroda/src/frontend/dap.rs | 223 ++++++++++++++++-- .../dap_rejects_non_initialize_first.stderr | 1 - .../dap_rejects_non_initialize_first.stdout | 4 +- 3 files changed, 203 insertions(+), 25 deletions(-) delete mode 100644 src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stderr diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index 2c4f41e4fb732..dcc497abe8b45 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -26,6 +26,15 @@ enum DispatchOutcome { Exit, } +#[derive(Clone, Copy, PartialEq, Eq)] +enum DapState { + Fresh, + Initialized, + Launched, + Stopped, + Terminated, +} + enum ExecutionOutcome { Stopped(StepResult), Terminated { code: i32 }, @@ -55,7 +64,7 @@ type DapServer = Server, io::StdoutLock<'static>>; /// Owns the DAP stdio transport and dispatches requests into Priroda handlers. struct DapSession { server: DapServer, - initialized: bool, + state: DapState, } impl DapSession { @@ -65,7 +74,7 @@ impl DapSession { BufReader::new(io::stdin().lock()), BufWriter::new(io::stdout().lock()), ), - initialized: false, + state: DapState::Fresh, } } @@ -95,11 +104,10 @@ impl DapSession { request: Request, session: &mut PrirodaContext<'tcx>, ) -> InterpResult<'tcx, ServerResult> { - // Reject non-initialize requests before the handshake completes so the - // client gets a framed error. - if !self.initialized && !matches!(&request.command, Command::Initialize(_)) { + if self.state == DapState::Fresh && !matches!(&request.command, Command::Initialize(_)) { return interp_ok( - self.handle_unsupported_request(request).map(|()| DispatchOutcome::Continue), + self.respond_error(request, "initialize must be sent first") + .map(|()| DispatchOutcome::Continue), ); } @@ -110,7 +118,7 @@ impl DapSession { interp_ok(self.handle_launch(request).map(|()| DispatchOutcome::Continue)), Command::ConfigurationDone => { let res = self.handle_configuration_done(request, session)?; - interp_ok(res.map(|()| DispatchOutcome::Continue)) + interp_ok(res.map(|()| self.dispatch_outcome())) } Command::Threads => interp_ok(self.handle_threads(request).map(|()| DispatchOutcome::Continue)), @@ -131,7 +139,7 @@ impl DapSession { _ => unreachable!(), }; let res = self.handle_step(request, body, session)?; - interp_ok(res.map(|()| DispatchOutcome::Continue)) + interp_ok(res.map(|()| self.dispatch_outcome())) } Command::Disconnect(_) => interp_ok(self.handle_disconnect(request).map(|()| DispatchOutcome::Exit)), @@ -144,8 +152,16 @@ impl DapSession { /// FIXME: connect launch arguments to Priroda's session model. fn handle_launch(&mut self, request: Request) -> ServerResult { + if self.reject_after_termination(&request)? + || self.require_state(&request, DapState::Initialized, "launch requires initialize")? + { + return Ok(()); + } + let response = request.success(ResponseBody::Launch); - self.server.respond(response) + self.server.respond(response)?; + self.state = DapState::Launched; + Ok(()) } fn handle_scopes<'tcx>( @@ -153,6 +169,13 @@ impl DapSession { request: Request, _session: &PrirodaContext<'tcx>, ) -> ServerResult { + if self.reject_after_termination(&request)? + || self.require_stopped(&request)? + || self.require_frame_id(&request)? + { + return Ok(()); + } + let response = request.success(ResponseBody::Scopes(ScopesResponse { scopes: vec![Scope { name: "Locals".to_string(), @@ -176,10 +199,16 @@ impl DapSession { request: Request, session: &PrirodaContext<'tcx>, ) -> ServerResult { + if self.reject_after_termination(&request)? + || self.require_stopped(&request)? + || self.require_variables_reference(&request)? + { + return Ok(()); + } + let variables = match &request.command { - Command::Variables(args) if args.variables_reference == LOCALS_VARIABLES_REFERENCE => + Command::Variables(_) => session.list_locals().into_iter().map(Self::local_to_variable).collect(), - Command::Variables(_) => Vec::new(), _ => unreachable!(), }; @@ -192,14 +221,21 @@ impl DapSession { request: Request, session: &mut PrirodaContext<'tcx>, ) -> InterpResult<'tcx, ServerResult> { + let rejected = match self.check_configuration_done_request(&request) { + Ok(rejected) => rejected, + Err(err) => return interp_ok(Err(err)), + }; + if rejected { + return interp_ok(Ok(())); + } + match Self::execution_outcome(session.stop_at_first_user_location()) { ExecutionOutcome::Stopped(_) => { let response = request.success(ResponseBody::ConfigurationDone); - interp_ok( - self.server - .respond(response) - .and_then(|()| self.send_stopped_event(StoppedEventReason::Entry)), - ) + interp_ok(self.server.respond(response).and_then(|()| { + self.state = DapState::Stopped; + self.send_stopped_event(StoppedEventReason::Entry) + })) } ExecutionOutcome::Terminated { code } => interp_ok(self.respond_terminated(request, ResponseBody::ConfigurationDone, code)), @@ -211,6 +247,10 @@ impl DapSession { /// FIXME: replace this with Miri thread state once Priroda exposes a /// frontend-facing thread model. fn handle_threads(&mut self, request: Request) -> ServerResult { + if self.reject_after_termination(&request)? { + return Ok(()); + } + let response = request.success(ResponseBody::Threads(ThreadsResponse { threads: vec![Thread { id: THREAD_ID, name: "main".to_string() }], })); @@ -223,6 +263,13 @@ impl DapSession { request: Request, session: &PrirodaContext<'tcx>, ) -> ServerResult { + if self.reject_after_termination(&request)? + || self.require_stopped(&request)? + || self.require_thread_id(&request)? + { + return Ok(()); + } + let stack_frames = match &session.current_location { Some(location) => { let path = session.local_path(location); @@ -271,13 +318,20 @@ impl DapSession { fn handle_initialize(&mut self, request: Request) -> ServerResult { // Advertise configurationDone support ahead of its handler so VS Code // completes the full handshake; the handler arrives in a later commit. + if self.reject_after_termination(&request)? { + return Ok(()); + } + if self.state != DapState::Fresh { + return self.respond_error(request, "initialize may only be sent once"); + } + let response = request.success(ResponseBody::Initialize(Capabilities { supports_configuration_done_request: Some(true), ..Capabilities::default() })); self.server.respond(response)?; self.server.send_event(Event::Initialized)?; - self.initialized = true; + self.state = DapState::Initialized; Ok(()) } @@ -288,13 +342,20 @@ impl DapSession { body: ResponseBody, session: &mut PrirodaContext<'tcx>, ) -> InterpResult<'tcx, ServerResult> { + let rejected = match self.check_step_request(&request) { + Ok(rejected) => rejected, + Err(err) => return interp_ok(Err(err)), + }; + if rejected { + return interp_ok(Ok(())); + } + match Self::execution_outcome(session.step()) { ExecutionOutcome::Stopped(result) => - interp_ok( - self.server - .respond(request.success(body)) - .and_then(|()| self.send_stopped_event(Self::stopped_reason(result))), - ), + interp_ok(self.server.respond(request.success(body)).and_then(|()| { + self.state = DapState::Stopped; + self.send_stopped_event(Self::stopped_reason(result)) + })), ExecutionOutcome::Terminated { code } => interp_ok(self.respond_terminated(request, body, code)), ExecutionOutcome::Failed(message) => @@ -304,6 +365,7 @@ impl DapSession { fn handle_disconnect(&mut self, request: Request) -> ServerResult { self.server.respond(request.success(ResponseBody::Disconnect))?; + self.state = DapState::Terminated; self.server.send_event(Event::Terminated(None)) } @@ -316,7 +378,123 @@ impl DapSession { self.server.respond(response) } + fn reject_after_termination(&mut self, request: &Request) -> ServerResult { + if self.state == DapState::Terminated { + self.server.respond(request.clone().error("request received after termination"))?; + return Ok(true); + } + + Ok(false) + } + + fn require_state( + &mut self, + request: &Request, + expected: DapState, + message: &'static str, + ) -> ServerResult { + if self.state != expected { + self.server.respond(request.clone().error(message))?; + return Ok(true); + } + + Ok(false) + } + + fn require_stopped(&mut self, request: &Request) -> ServerResult { + if self.state != DapState::Stopped { + self.server.respond(request.clone().error("request requires a stopped frame"))?; + return Ok(true); + } + + Ok(false) + } + + fn require_thread_id(&mut self, request: &Request) -> ServerResult { + let valid = match &request.command { + Command::StackTrace(args) => args.thread_id == THREAD_ID, + Command::Next(args) => args.thread_id == THREAD_ID, + Command::StepIn(args) => args.thread_id == THREAD_ID, + _ => unreachable!(), + }; + + if !valid { + self.server.respond(request.clone().error("unknown threadId"))?; + return Ok(true); + } + + Ok(false) + } + + fn require_frame_id(&mut self, request: &Request) -> ServerResult { + let Command::Scopes(args) = &request.command else { + unreachable!(); + }; + + if args.frame_id != STACK_FRAME_ID { + self.server.respond(request.clone().error("unknown frameId"))?; + return Ok(true); + } + + Ok(false) + } + + fn require_variables_reference(&mut self, request: &Request) -> ServerResult { + let Command::Variables(args) = &request.command else { + unreachable!(); + }; + + if args.variables_reference != LOCALS_VARIABLES_REFERENCE { + self.server.respond(request.clone().error("unknown variablesReference"))?; + return Ok(true); + } + + Ok(false) + } + + fn check_configuration_done_request(&mut self, request: &Request) -> ServerResult { + if self.reject_after_termination(request)? { + return Ok(true); + } + + if self.state == DapState::Stopped { + self.server + .respond(request.clone().error("configurationDone may only be sent once"))?; + return Ok(true); + } + + if self.require_state(request, DapState::Launched, "configurationDone requires launch")? { + return Ok(true); + } + + Ok(false) + } + + fn check_step_request(&mut self, request: &Request) -> ServerResult { + if self.reject_after_termination(request)? + || self.require_stopped(request)? + || self.require_thread_id(request)? + { + return Ok(true); + } + + Ok(false) + } + + fn respond_error(&mut self, request: Request, message: &str) -> ServerResult { + self.server.respond(request.error(message)) + } + + fn dispatch_outcome(&self) -> DispatchOutcome { + if self.state == DapState::Terminated { + DispatchOutcome::Exit + } else { + DispatchOutcome::Continue + } + } + fn respond_execution_error(&mut self, request: Request, message: String) -> ServerResult { + self.state = DapState::Terminated; self.server.respond(request.error(&message))?; self.server.send_event(Event::Terminated(None)) } @@ -327,6 +505,7 @@ impl DapSession { body: ResponseBody, code: i32, ) -> ServerResult { + self.state = DapState::Terminated; self.server.respond(request.success(body))?; self.server.send_event(Event::Exited(ExitedEventBody { exit_code: code.into() }))?; self.server.send_event(Event::Terminated(None))?; diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stderr b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stderr deleted file mode 100644 index 2641eb804868c..0000000000000 --- a/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stderr +++ /dev/null @@ -1 +0,0 @@ -priroda dap: unsupported request during DAP demo milestone: next diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout index 18ed56545ac9d..55ba4e862250f 100644 --- a/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout @@ -1,3 +1,3 @@ -Content-Length: 146 +Content-Length: 131 -{"seq":1,"type":"response","request_seq":2,"success":false,"message":"unsupported request in Priroda DAP demo mode","command":"next","error":null} \ No newline at end of file +{"seq":1,"type":"response","request_seq":2,"success":false,"message":"initialize must be sent first","command":"next","error":null} \ No newline at end of file From 51dbde5e56d473862979da4ea4cec95e96c0b8a5 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 2 Aug 2026 07:56:18 +0300 Subject: [PATCH 036/100] [Priroda] Add DAP negative protocol fixtures --- ...ejects_configuration_done_before_launch.rs | 3 +++ ...cts_configuration_done_before_launch.stdin | 7 ++++++ ...ts_configuration_done_before_launch.stdout | 11 ++++++++ ..._rejects_next_before_configuration_done.rs | 3 +++ ...jects_next_before_configuration_done.stdin | 9 +++++++ ...ects_next_before_configuration_done.stdout | 13 ++++++++++ ...dap_rejects_repeated_configuration_done.rs | 3 +++ ..._rejects_repeated_configuration_done.stdin | 11 ++++++++ ...rejects_repeated_configuration_done.stdout | 17 +++++++++++++ .../priroda/tests/ui/dap_rejects_wrong_ids.rs | 6 +++++ .../tests/ui/dap_rejects_wrong_ids.stdin | 19 ++++++++++++++ .../tests/ui/dap_rejects_wrong_ids.stdout | 25 +++++++++++++++++++ 12 files changed, 127 insertions(+) create mode 100644 src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.rs create mode 100644 src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdin create mode 100644 src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdout create mode 100644 src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.rs create mode 100644 src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdin create mode 100644 src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdout create mode 100644 src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.rs create mode 100644 src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdin create mode 100644 src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout create mode 100644 src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.rs create mode 100644 src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdin create mode 100644 src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdout diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.rs b/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdin b/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdin new file mode 100644 index 0000000000000..c1dedb5404eca --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdin @@ -0,0 +1,7 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 56 + +{"seq":2,"type":"request","command":"configurationDone"}Content-Length: 64 + +{"seq":3,"type":"request","command":"disconnect","arguments":{}} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdout new file mode 100644 index 0000000000000..1e3a55ff64ef5 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdout @@ -0,0 +1,11 @@ +Content-Length: 143 + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 + +{"seq":2,"type":"event","event":"initialized"}Content-Length: 148 + +{"seq":3,"type":"response","request_seq":2,"success":false,"message":"configurationDone requires launch","command":"configurationDone","error":null}Content-Length: 94 + +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"disconnect","error":null}Content-Length: 57 + +{"seq":5,"type":"event","event":"terminated","body":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.rs b/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdin b/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdin new file mode 100644 index 0000000000000..a582d4adc7fdb --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdin @@ -0,0 +1,9 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}}Content-Length: 70 + +{"seq":3,"type":"request","command":"next","arguments":{"threadId":1}}Content-Length: 65 + +{"seq":4,"type":"request","command":"disconnect","arguments":{}} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdout new file mode 100644 index 0000000000000..69906b4578351 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdout @@ -0,0 +1,13 @@ +Content-Length: 143 + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 + +{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 134 + +{"seq":4,"type":"response","request_seq":3,"success":false,"message":"request requires a stopped frame","command":"next","error":null}Content-Length: 94 + +{"seq":5,"type":"response","request_seq":4,"success":true,"command":"disconnect","error":null}Content-Length: 57 + +{"seq":6,"type":"event","event":"terminated","body":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.rs b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.rs new file mode 100644 index 0000000000000..c1f1ed6f67bea --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.rs @@ -0,0 +1,3 @@ +//@ compile-flags: --dap + +fn main() {} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdin b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdin new file mode 100644 index 0000000000000..d98039165e4d8 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdin @@ -0,0 +1,11 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}}Content-Length: 56 + +{"seq":3,"type":"request","command":"configurationDone"}Content-Length: 56 + +{"seq":4,"type":"request","command":"configurationDone"}Content-Length: 65 + +{"seq":5,"type":"request","command":"disconnect","arguments":{}} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout new file mode 100644 index 0000000000000..d81c109466e4c --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout @@ -0,0 +1,17 @@ +Content-Length: 143 + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 + +{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 101 + +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 186 + +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 154 + +{"seq":6,"type":"response","request_seq":4,"success":false,"message":"configurationDone may only be sent once","command":"configurationDone","error":null}Content-Length: 94 + +{"seq":7,"type":"response","request_seq":5,"success":true,"command":"disconnect","error":null}Content-Length: 57 + +{"seq":8,"type":"event","event":"terminated","body":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.rs b/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.rs new file mode 100644 index 0000000000000..cd7ad8e0bb32f --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.rs @@ -0,0 +1,6 @@ +//@ compile-flags: --dap + +fn main() { + let x = 1_i32; + let _ = x; +} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdin b/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdin new file mode 100644 index 0000000000000..a2a9dd1595bc8 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdin @@ -0,0 +1,19 @@ +Content-Length: 85 + +{"seq":1,"type":"request","command":"initialize","arguments":{"adapterID":"priroda"}}Content-Length: 60 + +{"seq":2,"type":"request","command":"launch","arguments":{}}Content-Length: 56 + +{"seq":3,"type":"request","command":"configurationDone"}Content-Length: 76 + +{"seq":4,"type":"request","command":"stackTrace","arguments":{"threadId":2}}Content-Length: 71 + +{"seq":5,"type":"request","command":"scopes","arguments":{"frameId":2}}Content-Length: 85 + +{"seq":6,"type":"request","command":"variables","arguments":{"variablesReference":2}}Content-Length: 70 + +{"seq":7,"type":"request","command":"next","arguments":{"threadId":2}}Content-Length: 72 + +{"seq":8,"type":"request","command":"stepIn","arguments":{"threadId":2}}Content-Length: 65 + +{"seq":9,"type":"request","command":"disconnect","arguments":{}} diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdout new file mode 100644 index 0000000000000..0230fe0e3e1c4 --- /dev/null +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdout @@ -0,0 +1,25 @@ +Content-Length: 143 + +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 + +{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 + +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 101 + +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 186 + +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 101 + +{"seq":6,"type":"response","request_seq":4,"success":false,"message":"unknown threadId","error":null}Content-Length: 100 + +{"seq":7,"type":"response","request_seq":5,"success":false,"message":"unknown frameId","error":null}Content-Length: 111 + +{"seq":8,"type":"response","request_seq":6,"success":false,"message":"unknown variablesReference","error":null}Content-Length: 118 + +{"seq":9,"type":"response","request_seq":7,"success":false,"message":"unknown threadId","command":"next","error":null}Content-Length: 121 + +{"seq":10,"type":"response","request_seq":8,"success":false,"message":"unknown threadId","command":"stepIn","error":null}Content-Length: 95 + +{"seq":11,"type":"response","request_seq":9,"success":true,"command":"disconnect","error":null}Content-Length: 58 + +{"seq":12,"type":"event","event":"terminated","body":null} \ No newline at end of file From 00c51d428cf1f43f5d1c5aee74e0154cbbbe50ff Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 2 Aug 2026 19:44:54 +0300 Subject: [PATCH 037/100] [Priroda] Use bug! for dispatch-guaranteed DAP invariants Replace `unreachable!()` with `bug!(...)` at the four dispatch-guaranteed invariant sites so they produce a meaningful message when the guard fails instead of a bare panic. Also switch the DAP error print to Debug format so transport errors include their chain. --- src/tools/miri/priroda/src/frontend/dap.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index dcc497abe8b45..b4b92018e1653 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -52,7 +52,7 @@ impl Dap { ) -> InterpResult<'tcx> { // FIXME: make this unbounded once Priroda has a full session lifecycle. if let Err(err) = DapSession::stdio().run_requests(session)? { - eprintln!("priroda dap error: {err}"); + eprintln!("priroda dap error: {err:?}"); } interp_ok(()) @@ -136,7 +136,7 @@ impl DapSession { let body = match &request.command { Command::Next(_) => ResponseBody::Next, Command::StepIn(_) => ResponseBody::StepIn, - _ => unreachable!(), + _ => bug!("step body is selected by the outer Next/StepIn match"), }; let res = self.handle_step(request, body, session)?; interp_ok(res.map(|()| self.dispatch_outcome())) @@ -209,7 +209,7 @@ impl DapSession { let variables = match &request.command { Command::Variables(_) => session.list_locals().into_iter().map(Self::local_to_variable).collect(), - _ => unreachable!(), + _ => bug!("dispatch routes only Variables to handle_variables"), }; let response = request.success(ResponseBody::Variables(VariablesResponse { variables })); @@ -428,7 +428,7 @@ impl DapSession { fn require_frame_id(&mut self, request: &Request) -> ServerResult { let Command::Scopes(args) = &request.command else { - unreachable!(); + bug!("dispatch routes only scopes to require_frame_id"); }; if args.frame_id != STACK_FRAME_ID { @@ -441,7 +441,7 @@ impl DapSession { fn require_variables_reference(&mut self, request: &Request) -> ServerResult { let Command::Variables(args) = &request.command else { - unreachable!(); + bug!("dispatch routes only variables to require_variables_reference"); }; if args.variables_reference != LOCALS_VARIABLES_REFERENCE { From f32ac7c2d097063ae5d66f0b88453c7a0e2bf42e Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 2 Aug 2026 19:45:15 +0300 Subject: [PATCH 038/100] [Priroda] Resolve macro-backed spans to their callsite Call `span.source_callsite()` in `resolve_current_location` so breakpoints and source reporting use the user-visible macro call site instead of the expanded macro body for lines generated by `println!`, `assert_eq!`, and similar macros. --- src/tools/miri/priroda/src/debugger.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tools/miri/priroda/src/debugger.rs b/src/tools/miri/priroda/src/debugger.rs index 557af87156b5f..07c9ddd2807fa 100644 --- a/src/tools/miri/priroda/src/debugger.rs +++ b/src/tools/miri/priroda/src/debugger.rs @@ -310,13 +310,12 @@ impl<'tcx> PrirodaContext<'tcx> { } fn resolve_current_location(&self) -> Option { - // FIXME: resolve macro-backed lines such as `println!` and `assert_eq!` - // through `span.source_callsite()` before matching breakpoints. let span = self.ecx.machine.current_user_relevant_span(); if span.is_dummy() { return None; } + let span = span.source_callsite(); let source_map = self.ecx.tcx.sess.source_map(); let loc = source_map.lookup_char_pos(span.lo()); From 1f184d7e966afe62a143fccf95857a1107230624 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 2 Aug 2026 19:24:32 +0300 Subject: [PATCH 039/100] [Priroda] Handle DAP continue request Add the `continue` command handler, reusing the existing source-line stepping and breakpoint infrastructure. The `handle_continue` method follows the same `ExecutionOutcome` dispatch pattern as `handle_step`. Include `Command::Continue` in `require_thread_id` validation so the request passes the thread-id guard, and widen the fallback from `unreachable!()` to `true` so any future request with a thread-id field passes validation rather than panicking. --- src/tools/miri/priroda/src/debugger.rs | 2 +- src/tools/miri/priroda/src/frontend/dap.rs | 37 ++++++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/tools/miri/priroda/src/debugger.rs b/src/tools/miri/priroda/src/debugger.rs index 07c9ddd2807fa..93fa8c2299dc0 100644 --- a/src/tools/miri/priroda/src/debugger.rs +++ b/src/tools/miri/priroda/src/debugger.rs @@ -173,7 +173,7 @@ impl<'tcx> PrirodaContext<'tcx> { } /// Continue execution until reaching a breakpoint or propagating termination. - fn continue_execution(&mut self) -> InterpResult<'tcx, StepResult> { + pub(super) fn continue_execution(&mut self) -> InterpResult<'tcx, StepResult> { self.resume(ResumeMode::Continue) } diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index b4b92018e1653..1d1caab50c664 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -2,7 +2,7 @@ use std::io::{self, BufReader, BufWriter}; use emmy_dap_types::prelude::events::{ExitedEventBody, StoppedEventBody}; use emmy_dap_types::prelude::responses::{ - ScopesResponse, StackTraceResponse, ThreadsResponse, VariablesResponse, + ContinueResponse, ScopesResponse, StackTraceResponse, ThreadsResponse, VariablesResponse, }; use emmy_dap_types::prelude::types::{ Capabilities, Scope, ScopePresentationhint, Source, StackFrame, StoppedEventReason, Thread, @@ -132,6 +132,10 @@ impl DapSession { interp_ok( self.handle_variables(request, session).map(|()| DispatchOutcome::Continue), ), + Command::Continue(_) => { + let res = self.handle_continue(request, session)?; + interp_ok(res.map(|()| self.dispatch_outcome())) + } Command::Next(_) | Command::StepIn(_) => { let body = match &request.command { Command::Next(_) => ResponseBody::Next, @@ -363,6 +367,34 @@ impl DapSession { } } + fn handle_continue<'tcx>( + &mut self, + request: Request, + session: &mut PrirodaContext<'tcx>, + ) -> InterpResult<'tcx, ServerResult> { + let rejected = match self.check_step_request(&request) { + Ok(rejected) => rejected, + Err(err) => return interp_ok(Err(err)), + }; + if rejected { + return interp_ok(Ok(())); + } + + let body = ResponseBody::Continue(ContinueResponse { all_threads_continued: Some(true) }); + + match Self::execution_outcome(session.continue_execution()) { + ExecutionOutcome::Stopped(result) => + interp_ok(self.server.respond(request.success(body)).and_then(|()| { + self.state = DapState::Stopped; + self.send_stopped_event(Self::stopped_reason(result)) + })), + ExecutionOutcome::Terminated { code } => + interp_ok(self.respond_terminated(request, body, code)), + ExecutionOutcome::Failed(message) => + interp_ok(self.respond_execution_error(request, message)), + } + } + fn handle_disconnect(&mut self, request: Request) -> ServerResult { self.server.respond(request.success(ResponseBody::Disconnect))?; self.state = DapState::Terminated; @@ -415,7 +447,8 @@ impl DapSession { Command::StackTrace(args) => args.thread_id == THREAD_ID, Command::Next(args) => args.thread_id == THREAD_ID, Command::StepIn(args) => args.thread_id == THREAD_ID, - _ => unreachable!(), + Command::Continue(args) => args.thread_id == THREAD_ID, + _ => true, }; if !valid { From affac58e3fd0b4f0746d9077b8eab0dab843039c Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 2 Aug 2026 19:26:45 +0300 Subject: [PATCH 040/100] [Priroda] Handle DAP setBreakpoints request Add the `setBreakpoints` command handler that maps DAP source breakpoints to the shared `PrirodaContext::set_breakpoint` breakpoint table. Every requested breakpoint is marked as verified so that VS Code displays the breakpoint marker in the editor gutter; path and line-range validation is deferred per the existing FIXME in `debugger.rs`. --- src/tools/miri/priroda/src/debugger.rs | 3 +- src/tools/miri/priroda/src/frontend/dap.rs | 51 ++++++++++++++++++++-- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/tools/miri/priroda/src/debugger.rs b/src/tools/miri/priroda/src/debugger.rs index 93fa8c2299dc0..aca596dc3f18e 100644 --- a/src/tools/miri/priroda/src/debugger.rs +++ b/src/tools/miri/priroda/src/debugger.rs @@ -177,7 +177,7 @@ impl<'tcx> PrirodaContext<'tcx> { self.resume(ResumeMode::Continue) } - fn set_breakpoint(&mut self, path: PathBuf, line: usize) -> BreakpointSetResult { + pub(super) fn set_breakpoint(&mut self, path: PathBuf, line: usize) -> BreakpointSetResult { // FIXME: validate breakpoints here so every frontend gets the same behavior. // Reject empty paths, missing files, directories, and line 0. Decide whether // out-of-range lines should be rejected or kept as pending breakpoints. @@ -305,7 +305,6 @@ impl<'tcx> PrirodaContext<'tcx> { fn current_breakpoint(&self) -> Option<(PathBuf, usize)> { let (path, line) = self.current_source_position()?; let lines = self.breakpoints.get(&path)?; - if lines.contains(&line) { Some((path, line)) } else { None } } diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index 1d1caab50c664..2e76ccdbb2a47 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -2,11 +2,12 @@ use std::io::{self, BufReader, BufWriter}; use emmy_dap_types::prelude::events::{ExitedEventBody, StoppedEventBody}; use emmy_dap_types::prelude::responses::{ - ContinueResponse, ScopesResponse, StackTraceResponse, ThreadsResponse, VariablesResponse, + ContinueResponse, ScopesResponse, SetBreakpointsResponse, StackTraceResponse, ThreadsResponse, + VariablesResponse, }; use emmy_dap_types::prelude::types::{ - Capabilities, Scope, ScopePresentationhint, Source, StackFrame, StoppedEventReason, Thread, - Variable, + Breakpoint as DapBreakpoint, Capabilities, Scope, ScopePresentationhint, Source, StackFrame, + StoppedEventReason, Thread, Variable, }; use emmy_dap_types::prelude::{Command, Event, Request, ResponseBody, Server}; use miri::{InterpErrorInfo, InterpErrorKind, InterpResult, TerminationInfo, bug, interp_ok}; @@ -136,6 +137,11 @@ impl DapSession { let res = self.handle_continue(request, session)?; interp_ok(res.map(|()| self.dispatch_outcome())) } + Command::SetBreakpoints(_) => + interp_ok( + self.handle_set_breakpoints(request, session) + .map(|()| DispatchOutcome::Continue), + ), Command::Next(_) | Command::StepIn(_) => { let body = match &request.command { Command::Next(_) => ResponseBody::Next, @@ -395,6 +401,45 @@ impl DapSession { } } + fn handle_set_breakpoints<'tcx>( + &mut self, + request: Request, + session: &mut PrirodaContext<'tcx>, + ) -> ServerResult { + if self.reject_after_termination(&request)? { + return Ok(()); + } + + let mut breakpoints = Vec::new(); + if let Command::SetBreakpoints(ref args) = request.command { + if let Some(ref path_str) = args.source.path { + let path = std::path::PathBuf::from(path_str); + if let Some(ref req_bps) = args.breakpoints { + for req_bp in req_bps { + let line = req_bp.line as usize; + session.set_breakpoint(path.clone(), line); + breakpoints.push(DapBreakpoint { + verified: true, + message: None, + source: Some(args.source.clone()), + line: Some(req_bp.line), + column: req_bp.column, + end_line: None, + end_column: None, + id: None, + instruction_reference: None, + offset: None, + }); + } + } + } + } + + let response = + request.success(ResponseBody::SetBreakpoints(SetBreakpointsResponse { breakpoints })); + self.server.respond(response) + } + fn handle_disconnect(&mut self, request: Request) -> ServerResult { self.server.respond(request.success(ResponseBody::Disconnect))?; self.state = DapState::Terminated; From 042a542512c4ed2d6532236d6c5e098130e66b52 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Sun, 2 Aug 2026 19:28:19 +0300 Subject: [PATCH 041/100] [Priroda] Advertise single-thread execution + unbounded loop Mark `supportsSingleThreadExecutionRequests: true` so that VS Code sends the `singleThread` flag on step/continue requests. This lets the editor drive the single-threaded prototype without protocol errors. Remove the `MAX_REQUEST_COUNT` guard and the `for 0..MAX_REQUEST_COUNT` loop, replacing them with a simple `loop {}`. The debug adapter now handles an unbounded number of requests, terminating only on disconnect or an explicit exit event. Set `source_reference: Some(0)` on stack frames so the editor does not request source content through `source` requests: the file is on disk and the editor can read it directly. Update all DAP `.stdout` fixture files to reflect the new capability field in the `initialize` response body. --- src/tools/miri/priroda/src/frontend/dap.rs | 9 +++------ .../miri/priroda/tests/ui/dap_initialize.stdout | 4 ++-- .../priroda/tests/ui/dap_initialize_launch.stdout | 4 ++-- .../dap_initialize_launch_configuration_done.stdout | 4 ++-- ...p_rejects_configuration_done_before_launch.stdout | 4 ++-- ...dap_rejects_next_before_configuration_done.stdout | 4 ++-- .../dap_rejects_repeated_configuration_done.stdout | 4 ++-- .../priroda/tests/ui/dap_rejects_wrong_ids.stdout | 4 ++-- .../priroda/tests/ui/dap_scopes_variables.stdout | 8 ++++---- .../tests/ui/dap_scopes_variables_next.stdout | 12 ++++++------ .../miri/priroda/tests/ui/dap_stack_trace.stdout | 8 ++++---- src/tools/miri/priroda/tests/ui/dap_threads.stdout | 4 ++-- 12 files changed, 33 insertions(+), 36 deletions(-) diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index 2e76ccdbb2a47..838fd273b9a3c 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -19,7 +19,6 @@ use crate::debugger::{LocalDesc, PrirodaContext, StepResult}; const THREAD_ID: i64 = 1; const STACK_FRAME_ID: i64 = 1; const LOCALS_VARIABLES_REFERENCE: i64 = 1; -const MAX_REQUEST_COUNT: usize = 128; type ServerResult = Result; enum DispatchOutcome { @@ -51,7 +50,6 @@ impl Dap { &self, session: &mut PrirodaContext<'tcx>, ) -> InterpResult<'tcx> { - // FIXME: make this unbounded once Priroda has a full session lifecycle. if let Err(err) = DapSession::stdio().run_requests(session)? { eprintln!("priroda dap error: {err:?}"); } @@ -83,7 +81,7 @@ impl DapSession { &mut self, session: &mut PrirodaContext<'tcx>, ) -> InterpResult<'tcx, ServerResult> { - for _ in 0..MAX_REQUEST_COUNT { + loop { let request = match self.server.poll_request() { Ok(Some(request)) => request, Ok(None) => return interp_ok(Ok(())), @@ -96,8 +94,6 @@ impl DapSession { Err(err) => return interp_ok(Err(err)), } } - - interp_ok(Ok(())) } fn dispatch_request<'tcx>( @@ -290,7 +286,7 @@ impl DapSession { Source { name: path.file_name().map(|name| name.to_string_lossy().into_owned()), path: Some(path.display().to_string()), - source_reference: None, + source_reference: Some(0), presentation_hint: None, origin: None, sources: None, @@ -337,6 +333,7 @@ impl DapSession { let response = request.success(ResponseBody::Initialize(Capabilities { supports_configuration_done_request: Some(true), + supports_single_thread_execution_requests: Some(true), ..Capabilities::default() })); self.server.respond(response)?; diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize.stdout b/src/tools/miri/priroda/tests/ui/dap_initialize.stdout index 595a84f405b40..8727976fe724c 100644 --- a/src/tools/miri/priroda/tests/ui/dap_initialize.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_initialize.stdout @@ -1,5 +1,5 @@ -Content-Length: 143 +Content-Length: 188 -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 {"seq":2,"type":"event","event":"initialized"} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdout b/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdout index 1bf97320e659e..79d465699abeb 100644 --- a/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdout @@ -1,6 +1,6 @@ -Content-Length: 143 +Content-Length: 188 -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 {"seq":2,"type":"event","event":"initialized"}Content-Length: 90 diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout index af8fbfd94f4fa..606f03229a6ef 100644 --- a/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout @@ -1,6 +1,6 @@ -Content-Length: 143 +Content-Length: 188 -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 {"seq":2,"type":"event","event":"initialized"}Content-Length: 90 diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdout index 1e3a55ff64ef5..b07888d49086c 100644 --- a/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdout @@ -1,6 +1,6 @@ -Content-Length: 143 +Content-Length: 188 -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 {"seq":2,"type":"event","event":"initialized"}Content-Length: 148 diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdout index 69906b4578351..1b63dca0d5d28 100644 --- a/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdout @@ -1,6 +1,6 @@ -Content-Length: 143 +Content-Length: 188 -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 {"seq":2,"type":"event","event":"initialized"}Content-Length: 90 diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout index d81c109466e4c..1079ba07ca74c 100644 --- a/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout @@ -1,6 +1,6 @@ -Content-Length: 143 +Content-Length: 188 -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 {"seq":2,"type":"event","event":"initialized"}Content-Length: 90 diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdout index 0230fe0e3e1c4..cd19d314bcfe3 100644 --- a/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdout @@ -1,6 +1,6 @@ -Content-Length: 143 +Content-Length: 188 -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 {"seq":2,"type":"event","event":"initialized"}Content-Length: 90 diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout index d7a47ffb2d178..5d21127c1b6ee 100644 --- a/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout @@ -1,6 +1,6 @@ -Content-Length: 143 +Content-Length: 188 -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 {"seq":2,"type":"event","event":"initialized"}Content-Length: 90 @@ -8,9 +8,9 @@ Content-Length: 143 {"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 186 -{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 304 +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 324 -{"seq":6,"type":"response","request_seq":4,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables.rs"},"line":4,"column":9}],"totalFrames":1},"error":null}Content-Length: 218 +{"seq":6,"type":"response","request_seq":4,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables.rs","sourceReference":0},"line":4,"column":9}],"totalFrames":1},"error":null}Content-Length: 218 {"seq":7,"type":"response","request_seq":5,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false}]},"error":null}Content-Length: 527 diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout index 3620bd1a0b9cb..58dc5937016fc 100644 --- a/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout @@ -1,6 +1,6 @@ -Content-Length: 143 +Content-Length: 188 -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 {"seq":2,"type":"event","event":"initialized"}Content-Length: 90 @@ -8,9 +8,9 @@ Content-Length: 143 {"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 186 -{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 314 +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 334 -{"seq":6,"type":"response","request_seq":4,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs"},"line":4,"column":9}],"totalFrames":1},"error":null}Content-Length: 218 +{"seq":6,"type":"response","request_seq":4,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs","sourceReference":0},"line":4,"column":9}],"totalFrames":1},"error":null}Content-Length: 218 {"seq":7,"type":"response","request_seq":5,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false}]},"error":null}Content-Length: 527 @@ -18,9 +18,9 @@ Content-Length: 143 {"seq":9,"type":"response","request_seq":7,"success":true,"command":"next","error":null}Content-Length: 186 -{"seq":10,"type":"event","event":"stopped","body":{"reason":"step","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 315 +{"seq":10,"type":"event","event":"stopped","body":{"reason":"step","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 335 -{"seq":11,"type":"response","request_seq":8,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs"},"line":5,"column":9}],"totalFrames":1},"error":null}Content-Length: 219 +{"seq":11,"type":"response","request_seq":8,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs","sourceReference":0},"line":5,"column":9}],"totalFrames":1},"error":null}Content-Length: 219 {"seq":12,"type":"response","request_seq":9,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false}]},"error":null}Content-Length: 528 diff --git a/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout b/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout index 318eefe286bac..4d2c505f23aec 100644 --- a/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout @@ -1,6 +1,6 @@ -Content-Length: 143 +Content-Length: 188 -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 {"seq":2,"type":"event","event":"initialized"}Content-Length: 90 @@ -10,6 +10,6 @@ Content-Length: 143 {"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 135 -{"seq":6,"type":"response","request_seq":4,"success":true,"command":"threads","body":{"threads":[{"id":1,"name":"main"}]},"error":null}Content-Length: 295 +{"seq":6,"type":"response","request_seq":4,"success":true,"command":"threads","body":{"threads":[{"id":1,"name":"main"}]},"error":null}Content-Length: 315 -{"seq":7,"type":"response","request_seq":5,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_stack_trace.rs","path":"{MANIFEST_DIR}/tests/ui/dap_stack_trace.rs"},"line":3,"column":11}],"totalFrames":1},"error":null} \ No newline at end of file +{"seq":7,"type":"response","request_seq":5,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_stack_trace.rs","path":"{MANIFEST_DIR}/tests/ui/dap_stack_trace.rs","sourceReference":0},"line":3,"column":11}],"totalFrames":1},"error":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_threads.stdout b/src/tools/miri/priroda/tests/ui/dap_threads.stdout index 953d114e22ec2..d9b5878b17adf 100644 --- a/src/tools/miri/priroda/tests/ui/dap_threads.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_threads.stdout @@ -1,6 +1,6 @@ -Content-Length: 143 +Content-Length: 188 -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 {"seq":2,"type":"event","event":"initialized"}Content-Length: 90 From adb5bb2502d75ee61139813eaaf0a207cd592056 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Mon, 3 Aug 2026 16:15:59 +0300 Subject: [PATCH 042/100] [Priroda] Normalise DAP Content-Length in test output DAP Content-Length headers embed the byte count of the following JSON, which drifts after path normalisation replaces the real manifest dir with {MANIFEST_DIR}. Replace Content-Length values with a {CONTENT_LENGTH} placeholder so path-length differences between machines do not cause spurious Content-Length mismatches in CI. --- src/tools/miri/priroda/tests/cli.rs | 6 ++++ .../priroda/tests/ui/dap_initialize.stdout | 4 +-- .../tests/ui/dap_initialize_launch.stdout | 6 ++-- ...nitialize_launch_configuration_done.stdout | 10 +++---- ...ts_configuration_done_before_launch.stdout | 10 +++---- ...ects_next_before_configuration_done.stdout | 12 ++++---- .../dap_rejects_non_initialize_first.stdout | 2 +- ...rejects_repeated_configuration_done.stdout | 16 +++++----- .../tests/ui/dap_rejects_wrong_ids.stdout | 24 +++++++-------- .../tests/ui/dap_scopes_variables.stdout | 16 +++++----- .../tests/ui/dap_scopes_variables_next.stdout | 30 +++++++++---------- .../priroda/tests/ui/dap_stack_trace.stdout | 14 ++++----- .../miri/priroda/tests/ui/dap_threads.stdout | 12 ++++---- 13 files changed, 84 insertions(+), 78 deletions(-) diff --git a/src/tools/miri/priroda/tests/cli.rs b/src/tools/miri/priroda/tests/cli.rs index ff2ce7716348a..2bf7f22bd1d98 100644 --- a/src/tools/miri/priroda/tests/cli.rs +++ b/src/tools/miri/priroda/tests/cli.rs @@ -34,6 +34,11 @@ fn main() -> Result<(), Box> { let rustc_sysroot_regex = Regex::new(®ex::escape(&rustc_sysroot)).unwrap(); let pointer_regex = Regex::new(r"0x[0-9a-f]+\[alloc[0-9]+\]<[0-9]+>").unwrap(); let crlf_regex = Regex::new(r"\r\n").unwrap(); + // DAP Content-Length headers embed the byte count of the following JSON, + // which changes when path normalisation alters the embedded file paths. + // Replace them with a placeholder so path-length differences between + // machines do not make Content-Length drift from the normalised body. + let content_length_regex = Regex::new(r"Content-Length: \d+").unwrap(); config.comment_defaults.base().normalize_stdout.extend([ (manifest_dir_regex.into(), b"{MANIFEST_DIR}".to_vec()), (miri_dir_regex.into(), b"{MIRI_DIR}".to_vec()), @@ -41,6 +46,7 @@ fn main() -> Result<(), Box> { (pointer_regex.into(), b"{ALLOC_PTR}".to_vec()), // DAP frames use CRLF headers; keep checked-in stdout fixtures readable. (crlf_regex.into(), b"\n".to_vec()), + (content_length_regex.into(), b"Content-Length: {CONTENT_LENGTH}".to_vec()), ]); // Priroda CLI tests do not currently require annotation comments in the test files diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize.stdout b/src/tools/miri/priroda/tests/ui/dap_initialize.stdout index 8727976fe724c..4f6f29a60dbd7 100644 --- a/src/tools/miri/priroda/tests/ui/dap_initialize.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_initialize.stdout @@ -1,5 +1,5 @@ -Content-Length: 188 +Content-Length: {CONTENT_LENGTH} -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} {"seq":2,"type":"event","event":"initialized"} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdout b/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdout index 79d465699abeb..7ba36709bd123 100644 --- a/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_initialize_launch.stdout @@ -1,7 +1,7 @@ -Content-Length: 188 +Content-Length: {CONTENT_LENGTH} -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} {"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout index 606f03229a6ef..121232f9aa271 100644 --- a/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_initialize_launch_configuration_done.stdout @@ -1,11 +1,11 @@ -Content-Length: 188 +Content-Length: {CONTENT_LENGTH} -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} -{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 101 +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 186 +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} {"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdout index b07888d49086c..4a4df53ea5889 100644 --- a/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_configuration_done_before_launch.stdout @@ -1,11 +1,11 @@ -Content-Length: 188 +Content-Length: {CONTENT_LENGTH} -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":2,"type":"event","event":"initialized"}Content-Length: 148 +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} -{"seq":3,"type":"response","request_seq":2,"success":false,"message":"configurationDone requires launch","command":"configurationDone","error":null}Content-Length: 94 +{"seq":3,"type":"response","request_seq":2,"success":false,"message":"configurationDone requires launch","command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":4,"type":"response","request_seq":3,"success":true,"command":"disconnect","error":null}Content-Length: 57 +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"disconnect","error":null}Content-Length: {CONTENT_LENGTH} {"seq":5,"type":"event","event":"terminated","body":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdout index 1b63dca0d5d28..796935374a8eb 100644 --- a/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_next_before_configuration_done.stdout @@ -1,13 +1,13 @@ -Content-Length: 188 +Content-Length: {CONTENT_LENGTH} -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} -{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 134 +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":4,"type":"response","request_seq":3,"success":false,"message":"request requires a stopped frame","command":"next","error":null}Content-Length: 94 +{"seq":4,"type":"response","request_seq":3,"success":false,"message":"request requires a stopped frame","command":"next","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":5,"type":"response","request_seq":4,"success":true,"command":"disconnect","error":null}Content-Length: 57 +{"seq":5,"type":"response","request_seq":4,"success":true,"command":"disconnect","error":null}Content-Length: {CONTENT_LENGTH} {"seq":6,"type":"event","event":"terminated","body":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout index 55ba4e862250f..7ad4e38819f8f 100644 --- a/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_non_initialize_first.stdout @@ -1,3 +1,3 @@ -Content-Length: 131 +Content-Length: {CONTENT_LENGTH} {"seq":1,"type":"response","request_seq":2,"success":false,"message":"initialize must be sent first","command":"next","error":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout index 1079ba07ca74c..c7d7b63bf5608 100644 --- a/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout @@ -1,17 +1,17 @@ -Content-Length: 188 +Content-Length: {CONTENT_LENGTH} -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} -{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 101 +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 186 +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 154 +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: {CONTENT_LENGTH} -{"seq":6,"type":"response","request_seq":4,"success":false,"message":"configurationDone may only be sent once","command":"configurationDone","error":null}Content-Length: 94 +{"seq":6,"type":"response","request_seq":4,"success":false,"message":"configurationDone may only be sent once","command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":7,"type":"response","request_seq":5,"success":true,"command":"disconnect","error":null}Content-Length: 57 +{"seq":7,"type":"response","request_seq":5,"success":true,"command":"disconnect","error":null}Content-Length: {CONTENT_LENGTH} {"seq":8,"type":"event","event":"terminated","body":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdout index cd19d314bcfe3..6baf6351f6a7b 100644 --- a/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_wrong_ids.stdout @@ -1,25 +1,25 @@ -Content-Length: 188 +Content-Length: {CONTENT_LENGTH} -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} -{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 101 +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 186 +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 101 +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: {CONTENT_LENGTH} -{"seq":6,"type":"response","request_seq":4,"success":false,"message":"unknown threadId","error":null}Content-Length: 100 +{"seq":6,"type":"response","request_seq":4,"success":false,"message":"unknown threadId","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":7,"type":"response","request_seq":5,"success":false,"message":"unknown frameId","error":null}Content-Length: 111 +{"seq":7,"type":"response","request_seq":5,"success":false,"message":"unknown frameId","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":8,"type":"response","request_seq":6,"success":false,"message":"unknown variablesReference","error":null}Content-Length: 118 +{"seq":8,"type":"response","request_seq":6,"success":false,"message":"unknown variablesReference","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":9,"type":"response","request_seq":7,"success":false,"message":"unknown threadId","command":"next","error":null}Content-Length: 121 +{"seq":9,"type":"response","request_seq":7,"success":false,"message":"unknown threadId","command":"next","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":10,"type":"response","request_seq":8,"success":false,"message":"unknown threadId","command":"stepIn","error":null}Content-Length: 95 +{"seq":10,"type":"response","request_seq":8,"success":false,"message":"unknown threadId","command":"stepIn","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":11,"type":"response","request_seq":9,"success":true,"command":"disconnect","error":null}Content-Length: 58 +{"seq":11,"type":"response","request_seq":9,"success":true,"command":"disconnect","error":null}Content-Length: {CONTENT_LENGTH} {"seq":12,"type":"event","event":"terminated","body":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout index 5d21127c1b6ee..70c0765611115 100644 --- a/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout @@ -1,17 +1,17 @@ -Content-Length: 188 +Content-Length: {CONTENT_LENGTH} -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} -{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 101 +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 186 +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 324 +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: {CONTENT_LENGTH} -{"seq":6,"type":"response","request_seq":4,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables.rs","sourceReference":0},"line":4,"column":9}],"totalFrames":1},"error":null}Content-Length: 218 +{"seq":6,"type":"response","request_seq":4,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables.rs","sourceReference":0},"line":4,"column":9}],"totalFrames":1},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":7,"type":"response","request_seq":5,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false}]},"error":null}Content-Length: 527 +{"seq":7,"type":"response","request_seq":5,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false}]},"error":null}Content-Length: {CONTENT_LENGTH} {"seq":8,"type":"response","request_seq":6,"success":true,"command":"variables","body":{"variables":[{"name":"_0","value":"","type":"()","variablesReference":0},{"name":"x","value":"","type":"i32","variablesReference":0},{"name":"y","value":"","type":"bool","variablesReference":0},{"name":"_3","value":"","type":"(i32, bool)","variablesReference":0},{"name":"_4","value":"","type":"i32","variablesReference":0},{"name":"_5","value":"","type":"bool","variablesReference":0}]},"error":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout index 58dc5937016fc..68e2e00bd74db 100644 --- a/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout @@ -1,31 +1,31 @@ -Content-Length: 188 +Content-Length: {CONTENT_LENGTH} -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} -{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 101 +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 186 +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 334 +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: {CONTENT_LENGTH} -{"seq":6,"type":"response","request_seq":4,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs","sourceReference":0},"line":4,"column":9}],"totalFrames":1},"error":null}Content-Length: 218 +{"seq":6,"type":"response","request_seq":4,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs","sourceReference":0},"line":4,"column":9}],"totalFrames":1},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":7,"type":"response","request_seq":5,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false}]},"error":null}Content-Length: 527 +{"seq":7,"type":"response","request_seq":5,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false}]},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":8,"type":"response","request_seq":6,"success":true,"command":"variables","body":{"variables":[{"name":"_0","value":"","type":"()","variablesReference":0},{"name":"x","value":"","type":"i32","variablesReference":0},{"name":"y","value":"","type":"bool","variablesReference":0},{"name":"_3","value":"","type":"(i32, bool)","variablesReference":0},{"name":"_4","value":"","type":"i32","variablesReference":0},{"name":"_5","value":"","type":"bool","variablesReference":0}]},"error":null}Content-Length: 88 +{"seq":8,"type":"response","request_seq":6,"success":true,"command":"variables","body":{"variables":[{"name":"_0","value":"","type":"()","variablesReference":0},{"name":"x","value":"","type":"i32","variablesReference":0},{"name":"y","value":"","type":"bool","variablesReference":0},{"name":"_3","value":"","type":"(i32, bool)","variablesReference":0},{"name":"_4","value":"","type":"i32","variablesReference":0},{"name":"_5","value":"","type":"bool","variablesReference":0}]},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":9,"type":"response","request_seq":7,"success":true,"command":"next","error":null}Content-Length: 186 +{"seq":9,"type":"response","request_seq":7,"success":true,"command":"next","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":10,"type":"event","event":"stopped","body":{"reason":"step","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 335 +{"seq":10,"type":"event","event":"stopped","body":{"reason":"step","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: {CONTENT_LENGTH} -{"seq":11,"type":"response","request_seq":8,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs","sourceReference":0},"line":5,"column":9}],"totalFrames":1},"error":null}Content-Length: 219 +{"seq":11,"type":"response","request_seq":8,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs","sourceReference":0},"line":5,"column":9}],"totalFrames":1},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":12,"type":"response","request_seq":9,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false}]},"error":null}Content-Length: 528 +{"seq":12,"type":"response","request_seq":9,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false}]},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":13,"type":"response","request_seq":10,"success":true,"command":"variables","body":{"variables":[{"name":"_0","value":"","type":"()","variablesReference":0},{"name":"x","value":"1_i32","type":"i32","variablesReference":0},{"name":"y","value":"","type":"bool","variablesReference":0},{"name":"_3","value":"","type":"(i32, bool)","variablesReference":0},{"name":"_4","value":"","type":"i32","variablesReference":0},{"name":"_5","value":"","type":"bool","variablesReference":0}]},"error":null}Content-Length: 96 +{"seq":13,"type":"response","request_seq":10,"success":true,"command":"variables","body":{"variables":[{"name":"_0","value":"","type":"()","variablesReference":0},{"name":"x","value":"1_i32","type":"i32","variablesReference":0},{"name":"y","value":"","type":"bool","variablesReference":0},{"name":"_3","value":"","type":"(i32, bool)","variablesReference":0},{"name":"_4","value":"","type":"i32","variablesReference":0},{"name":"_5","value":"","type":"bool","variablesReference":0}]},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":14,"type":"response","request_seq":11,"success":true,"command":"disconnect","error":null}Content-Length: 58 +{"seq":14,"type":"response","request_seq":11,"success":true,"command":"disconnect","error":null}Content-Length: {CONTENT_LENGTH} {"seq":15,"type":"event","event":"terminated","body":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout b/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout index 4d2c505f23aec..1056d39e468b1 100644 --- a/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_stack_trace.stdout @@ -1,15 +1,15 @@ -Content-Length: 188 +Content-Length: {CONTENT_LENGTH} -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} -{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 101 +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 186 +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 135 +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: {CONTENT_LENGTH} -{"seq":6,"type":"response","request_seq":4,"success":true,"command":"threads","body":{"threads":[{"id":1,"name":"main"}]},"error":null}Content-Length: 315 +{"seq":6,"type":"response","request_seq":4,"success":true,"command":"threads","body":{"threads":[{"id":1,"name":"main"}]},"error":null}Content-Length: {CONTENT_LENGTH} {"seq":7,"type":"response","request_seq":5,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_stack_trace.rs","path":"{MANIFEST_DIR}/tests/ui/dap_stack_trace.rs","sourceReference":0},"line":3,"column":11}],"totalFrames":1},"error":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_threads.stdout b/src/tools/miri/priroda/tests/ui/dap_threads.stdout index d9b5878b17adf..56702d4adc22e 100644 --- a/src/tools/miri/priroda/tests/ui/dap_threads.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_threads.stdout @@ -1,13 +1,13 @@ -Content-Length: 188 +Content-Length: {CONTENT_LENGTH} -{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: 46 +{"seq":1,"type":"response","request_seq":1,"success":true,"command":"initialize","body":{"supportsConfigurationDoneRequest":true,"supportsSingleThreadExecutionRequests":true},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":2,"type":"event","event":"initialized"}Content-Length: 90 +{"seq":2,"type":"event","event":"initialized"}Content-Length: {CONTENT_LENGTH} -{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: 101 +{"seq":3,"type":"response","request_seq":2,"success":true,"command":"launch","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: 186 +{"seq":4,"type":"response","request_seq":3,"success":true,"command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} -{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: 135 +{"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: {CONTENT_LENGTH} {"seq":6,"type":"response","request_seq":4,"success":true,"command":"threads","body":{"threads":[{"id":1,"name":"main"}]},"error":null} \ No newline at end of file From 8427cb5e21a84525d50f987f482e571e2fa300cd Mon Sep 17 00:00:00 2001 From: zakrad <49591476+zakrad@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:56:19 +0330 Subject: [PATCH 043/100] Add regression test for associated type outlives bound at call site --- ...oc-type-outlives-via-where-clause-63253.rs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/ui/associated-types/assoc-type-outlives-via-where-clause-63253.rs diff --git a/tests/ui/associated-types/assoc-type-outlives-via-where-clause-63253.rs b/tests/ui/associated-types/assoc-type-outlives-via-where-clause-63253.rs new file mode 100644 index 0000000000000..ad675d4bc848e --- /dev/null +++ b/tests/ui/associated-types/assoc-type-outlives-via-where-clause-63253.rs @@ -0,0 +1,33 @@ +//! Regression test for . +//! +//! A `where Self::Ty: 'a` bound on the callee was not being used to prove the +//! associated type outlives `'a` at the call site, so both of these calls used +//! to fail with E0309 ("the associated type `>::Ty` may not live +//! long enough"). + +//@ check-pass + +#![allow(unused)] + +// The associated function is reached through a method-call path. +trait Trait<'a> { + type Ty; + fn method(ty_ref: &'a Self::Ty) where Self::Ty: 'a {} +} + +fn caller<'a, T: Trait<'a>>(arg: &'a T::Ty) where T::Ty: 'a { + T::method(arg) +} + +// The same bound, reached through a free function instead. +trait Trait2<'a> { + type Ty; +} + +fn free_fn<'a, T: Trait2<'a>>(_arg: &'a T::Ty) where T::Ty: 'a {} + +fn free_fn_caller<'a, T: Trait2<'a>>(arg: &'a T::Ty) where T::Ty: 'a { + free_fn::(arg) +} + +fn main() {} From cd19505b4ae92957e6d631c92a0bb325691aee73 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:44:54 +0200 Subject: [PATCH 044/100] rustc_resolve: move diagnostic attribute linting to attr parsing It had to be in rustc_resolve because there was no general attribute parsing infra back then, but now there is, so it should be there --- .../src/attributes/diagnostic/mod.rs | 68 ++++++++++++++++++- .../src/attributes/diagnostic/on_const.rs | 8 +-- .../src/attributes/diagnostic/on_move.rs | 34 ++++------ .../attributes/diagnostic/on_type_error.rs | 34 +++------- .../src/attributes/diagnostic/on_unknown.rs | 34 ++++------ .../diagnostic/on_unmatched_args.rs | 8 +-- .../src/attributes/diagnostic/opaque.rs | 7 +- .../rustc_attr_parsing/src/diagnostics.rs | 29 ++++++++ compiler/rustc_attr_parsing/src/interface.rs | 3 + compiler/rustc_attr_parsing/src/lib.rs | 2 + compiler/rustc_resolve/src/diagnostics/mod.rs | 24 ------- compiler/rustc_resolve/src/macros.rs | 58 +--------------- .../feature-gate-diagnostic-on-move.stderr | 5 +- ...nostic-on-type-error-malformed-args.stderr | 5 +- .../feature-gate-diagnostic-on-type-error.rs | 1 + ...ature-gate-diagnostic-on-type-error.stderr | 9 +-- .../feature-gate-diagnostic-on-unknown.stderr | 5 +- .../feature-gate-diagnostic-opaque.stderr | 10 +-- 18 files changed, 165 insertions(+), 179 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index 1264ad6597561..a813e0ba02a89 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -1,16 +1,20 @@ use std::ops::Range; +use rustc_ast::PathSegment; +use rustc_errors::{Diagnostic, MultiSpan}; use rustc_hir::attrs::diagnostic::{ Directive, Filter, FilterFormatString, Flag, FormatArg, FormatString, LitOrArg, Name, NameValue, Piece, Predicate, }; +use rustc_lint_defs::LintId; use rustc_parse_format::{ Argument, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece, Position, }; use rustc_session::lint::builtin::{ MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FILTERS, - MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, UNKNOWN_DIAGNOSTIC_ATTRIBUTES, }; +use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::{Ident, InnerSpan, Span, Symbol, kw, sym}; use thin_vec::{ThinVec, thin_vec}; @@ -20,6 +24,7 @@ use crate::diagnostics::{ MissingOptionsForDiagnosticAttribute, NonMetaItemDiagnosticAttribute, WrappedParserError, }; use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser}; +use crate::{EmitAttribute, diagnostics}; pub(crate) mod do_not_recommend; pub(crate) mod on_const; @@ -30,6 +35,67 @@ pub(crate) mod on_unknown; pub(crate) mod on_unmatched_args; pub(crate) mod opaque; +impl<'sess> crate::AttributeParser<'sess> { + pub(crate) fn unknown_diagnostic_attr( + &self, + segment: &PathSegment, + mut emit_lint: impl FnMut(LintId, MultiSpan, EmitAttribute), + ) { + const DIAGNOSTIC_ATTRIBUTES: [( + Symbol, /* name */ + Option, /* feature gate */ + ); 8] = [ + (sym::on_unimplemented, None), + (sym::do_not_recommend, None), + (sym::on_move, Some(sym::diagnostic_on_move)), + (sym::on_const, Some(sym::diagnostic_on_const)), + (sym::on_unknown, Some(sym::diagnostic_on_unknown)), + (sym::on_unmatched_args, Some(sym::diagnostic_on_unmatched_args)), + (sym::on_type_error, Some(sym::diagnostic_on_type_error)), + (sym::opaque, Some(sym::diagnostic_opaque)), + ]; + // No need to emit a lint if features aren't available. + let Some(features) = self.features else { return }; + let span = segment.span(); + let candidates = DIAGNOSTIC_ATTRIBUTES + .iter() + .filter_map(|(attr, feature)| { + feature.is_none_or(|f| features.enabled(f)).then_some(*attr) + }) + .collect::>(); + + let typo = find_best_match_for_name(&candidates, segment.ident.name, None) + .map(|typo_name| diagnostics::UnknownDiagnosticAttributeTypo { span, typo_name }); + emit_lint( + LintId::of(UNKNOWN_DIAGNOSTIC_ATTRIBUTES), + span.into(), + EmitAttribute(Box::new(move |dcx, level, _| { + diagnostics::UnknownDiagnosticAttribute { typo }.into_diag(dcx, level) + })), + ) + } +} + +#[rustc_macro_transparency = "transparent"] +macro gate_diagnostic_attr($feature:ident) {{ + if let Some(features) = cx.features_option() + && !features.$feature() + { + args.ignore_args(); + let nightly_build = cx.sess.is_nightly_build(); + let span = cx.attr_span; + cx.emit_lint( + rustc_lint_defs::builtin::UNKNOWN_DIAGNOSTIC_ATTRIBUTES, + $crate::diagnostics::UnstableDiagnosticAttribute { + feature: sym::$feature, + nightly_build, + }, + span, + ); + return; + } +}} + #[derive(Copy, Clone)] pub(crate) enum Mode { /// `#[rustc_on_unimplemented]` diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs index 20c0ac7cc8554..f92a7694ec357 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs @@ -13,13 +13,9 @@ impl AttributeParser for OnConstParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::diagnostic, sym::on_const], template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), - AttributeStability::Stable, // Unstable, stability checked manually in the parser + AttributeStability::Stable, // Unstable, stability checked manually below |this, cx, args| { - if !cx.features().diagnostic_on_const() { - // `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs - args.ignore_args(); - return; - } + gate_diagnostic_attr!(diagnostic_on_const); let path_span = cx.attr_path.span; this.path_span = Some(path_span); diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs index 1c8b3418fa746..dcba5d5b301a6 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs @@ -4,8 +4,6 @@ use rustc_span::sym; use crate::attributes::diagnostic::*; use crate::attributes::prelude::*; -use crate::context::AcceptContext; -use crate::parser::ArgParser; use crate::target_checking::AllowedTargets; use crate::template; @@ -15,31 +13,23 @@ pub(crate) struct OnMoveParser { directive: Option<(Span, Directive)>, } -impl OnMoveParser { - fn parse<'sess>(&mut self, cx: &mut AcceptContext<'_, 'sess>, args: &ArgParser, mode: Mode) { - if !cx.features().diagnostic_on_move() { - // `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs - args.ignore_args(); - return; - } - - let span = cx.attr_span; - self.span = Some(span); - - let Some(items) = parse_list(cx, args, mode) else { return }; - - if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) { - merge_directives(cx, &mut self.directive, (span, directive)); - } - } -} impl AttributeParser for OnMoveParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::diagnostic, sym::on_move], template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), - AttributeStability::Stable, // Unstable, stability checked manually in the parser + AttributeStability::Stable, // Unstable, stability checked manually below |this, cx, args| { - this.parse(cx, args, Mode::DiagnosticOnMove); + gate_diagnostic_attr!(diagnostic_on_move); + + let span = cx.attr_span; + this.span = Some(span); + let mode = Mode::DiagnosticOnMove; + + let Some(items) = parse_list(cx, args, mode) else { return }; + + if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) { + merge_directives(cx, &mut this.directive, (span, directive)); + } }, )]; diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_type_error.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_type_error.rs index 38c1f9ab6c945..1bdde3af7f0eb 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_type_error.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_type_error.rs @@ -4,8 +4,6 @@ use rustc_span::sym; use crate::attributes::AttributeStability; use crate::attributes::diagnostic::*; use crate::attributes::prelude::*; -use crate::context::AcceptContext; -use crate::parser::ArgParser; use crate::target_checking::AllowedTargets; use crate::template; @@ -15,32 +13,22 @@ pub(crate) struct OnTypeErrorParser { directive: Option<(Span, Directive)>, } -impl OnTypeErrorParser { - fn parse<'sess>(&mut self, cx: &mut AcceptContext<'_, 'sess>, args: &ArgParser, mode: Mode) { - if !cx.features().diagnostic_on_type_error() { - // `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs - args.ignore_args(); - return; - } - - let span = cx.attr_span; - self.span = Some(span); - - let Some(items) = parse_list(cx, args, mode) else { return }; - - if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) { - merge_directives(cx, &mut self.directive, (span, directive)); - } - } -} - impl AttributeParser for OnTypeErrorParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::diagnostic, sym::on_type_error], template!(List: &[r#"note = "...""#]), - AttributeStability::Stable, + AttributeStability::Stable, // Unstable, stability checked manually below |this, cx, args| { - this.parse(cx, args, Mode::DiagnosticOnTypeError); + gate_diagnostic_attr!(diagnostic_on_type_error); + + let span = cx.attr_span; + this.span = Some(span); + let mode = Mode::DiagnosticOnTypeError; + let Some(items) = parse_list(cx, args, mode) else { return }; + + if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) { + merge_directives(cx, &mut this.directive, (span, directive)); + } }, )]; diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs index bfa26d993b17e..029d971910e5e 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs @@ -10,33 +10,23 @@ pub(crate) struct OnUnknownParser { directive: Option<(Span, Directive)>, } -impl OnUnknownParser { - fn parse<'sess>(&mut self, cx: &mut AcceptContext<'_, 'sess>, args: &ArgParser, mode: Mode) { - if let Some(features) = cx.features - && !features.diagnostic_on_unknown() - { - // `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs - args.ignore_args(); - return; - } - let span = cx.attr_span; - self.span = Some(span); - - let Some(items) = parse_list(cx, args, mode) else { return }; - - if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) { - merge_directives(cx, &mut self.directive, (span, directive)); - }; - } -} - impl AttributeParser for OnUnknownParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::diagnostic, sym::on_unknown], template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), - AttributeStability::Stable, // Unstable, stability checked manually in the parser + AttributeStability::Stable, // Unstable, stability checked manually below |this, cx, args| { - this.parse(cx, args, Mode::DiagnosticOnUnknown); + gate_diagnostic_attr!(diagnostic_on_unknown); + + let span = cx.attr_span; + this.span = Some(span); + let mode = Mode::DiagnosticOnUnknown; + + let Some(items) = parse_list(cx, args, mode) else { return }; + + if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) { + merge_directives(cx, &mut this.directive, (span, directive)); + }; }, )]; // "Allowed" for all targets, but noop for all but use statements. diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatched_args.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatched_args.rs index df8cee63506cc..41ed6df43063e 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatched_args.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unmatched_args.rs @@ -14,13 +14,9 @@ impl AttributeParser for OnUnmatchedArgsParser { const ATTRIBUTES: AcceptMapping = &[( &[sym::diagnostic, sym::on_unmatched_args], template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), - AttributeStability::Stable, // Unstable, stability checked manually in the parser + AttributeStability::Stable, // Unstable, stability checked manually below |this, cx, args| { - if !cx.features().diagnostic_on_unmatched_args() { - // `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs - args.ignore_args(); - return; - } + gate_diagnostic_attr!(diagnostic_on_unmatched_args); let span = cx.attr_span; this.span = Some(span); diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/opaque.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/opaque.rs index 64c5ed704f8c5..0864b76f0e030 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/opaque.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/opaque.rs @@ -4,6 +4,7 @@ use rustc_hir::attrs::AttributeKind; use rustc_session::lint::builtin::MALFORMED_DIAGNOSTIC_ATTRIBUTES; use rustc_span::{Span, sym}; +use crate::attributes::diagnostic::gate_diagnostic_attr; use crate::attributes::{AcceptMapping, AttributeParser}; use crate::context::{AcceptContext, FinalizeContext}; use crate::diagnostics::OpaqueDoesNotExpectArgs; @@ -22,11 +23,9 @@ impl AttributeParser for OpaqueParser { ( &[sym::diagnostic, sym::opaque], template!(Word), - AttributeStability::Stable, // Unstable, stability checked manually in the parser + AttributeStability::Stable, // Unstable, stability checked manually below |this, cx, args| { - if !cx.features().diagnostic_opaque() { - return; - } + gate_diagnostic_attr!(diagnostic_opaque); this.parse(cx, args); }, ), diff --git a/compiler/rustc_attr_parsing/src/diagnostics.rs b/compiler/rustc_attr_parsing/src/diagnostics.rs index f9ebd78580b4c..1e76ab44826a9 100644 --- a/compiler/rustc_attr_parsing/src/diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/diagnostics.rs @@ -848,3 +848,32 @@ pub(crate) struct ToolReserved { pub(crate) span: Span, pub(crate) tool: Ident, } + +#[derive(Diagnostic)] +#[diag("unknown diagnostic attribute")] +pub(crate) struct UnknownDiagnosticAttribute { + #[subdiagnostic] + pub typo: Option, +} + +#[derive(Subdiagnostic)] +#[suggestion( + "an attribute with a similar name exists", + style = "verbose", + code = "{typo_name}", + applicability = "machine-applicable" +)] +pub(crate) struct UnknownDiagnosticAttributeTypo { + #[primary_span] + pub span: Span, + pub typo_name: Symbol, +} + +#[derive(Diagnostic)] +#[diag("unknown diagnostic attribute")] +pub(crate) struct UnstableDiagnosticAttribute { + #[note("this is an experimental diagnostic attribute")] + #[help("add `#![feature({$feature})]` to the crate attributes to enable")] + pub nightly_build: bool, + pub feature: Symbol, +} diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index cea549e310476..73108e42d5ab5 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -292,6 +292,7 @@ impl<'sess> AttributeParser<'sess> { self.sess } + #[track_caller] pub(crate) fn features(&self) -> &'sess Features { self.features.expect("features not available at this point in the compiler") } @@ -451,6 +452,8 @@ impl<'sess> AttributeParser<'sess> { if !cx.shared.has_lint_been_emitted.load(Ordering::Relaxed) { cx.shared.cx.check_args_used(attr, &args) } + } else if let [sym::diagnostic, _unknown, ..] = &*parts { + self.unknown_diagnostic_attr(&n.item.path.segments[1], &mut emit_lint); } else { let attr = AttrItem { path: attr_path.clone(), diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index 1b58a9aae5abe..bcdb401bc08c3 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -87,9 +87,11 @@ //! [`rustc_passes::check_attr`]: ../rustc_passes/check_attr/index.html // tidy-alphabetical-start +#![expect(internal_features, reason = "rustc_attrs")] #![feature(decl_macro)] #![feature(deref_patterns)] #![feature(iter_intersperse)] +#![feature(rustc_attrs)] #![feature(try_blocks)] #![recursion_limit = "256"] // tidy-alphabetical-end diff --git a/compiler/rustc_resolve/src/diagnostics/mod.rs b/compiler/rustc_resolve/src/diagnostics/mod.rs index cadfab22c8862..9053d45a41191 100644 --- a/compiler/rustc_resolve/src/diagnostics/mod.rs +++ b/compiler/rustc_resolve/src/diagnostics/mod.rs @@ -1503,30 +1503,6 @@ pub(crate) struct RedundantImportVisibility { pub max_vis: String, } -#[derive(Diagnostic)] -#[diag("unknown diagnostic attribute")] -pub(crate) struct UnknownDiagnosticAttribute { - #[subdiagnostic] - pub help: Option, -} - -#[derive(Subdiagnostic)] -pub(crate) enum UnknownDiagnosticAttributeHelp { - #[suggestion( - "an attribute with a similar name exists", - style = "verbose", - code = "{typo_name}", - applicability = "machine-applicable" - )] - Typo { - #[primary_span] - span: Span, - typo_name: Symbol, - }, - #[help("add `#![feature({$feature})]` to the crate attributes to enable")] - UseFeature { feature: Symbol }, -} - // FIXME: Make this properly translatable. pub(crate) struct Ambiguity { pub ident: Ident, diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 1e9d60ca21551..812b769561054 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -25,11 +25,9 @@ use rustc_middle::ty::{RegisteredTools, TyCtxt}; use rustc_session::Session; use rustc_session::diagnostics::feature_err; use rustc_session::lint::builtin::{ - LEGACY_DERIVE_HELPERS, OUT_OF_SCOPE_MACRO_CALLS, UNKNOWN_DIAGNOSTIC_ATTRIBUTES, - UNUSED_MACRO_RULES, UNUSED_MACROS, + LEGACY_DERIVE_HELPERS, OUT_OF_SCOPE_MACRO_CALLS, UNUSED_MACRO_RULES, UNUSED_MACROS, }; use rustc_span::def_id::ModId; -use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::edition::Edition; use rustc_span::hygiene::{self, AstPass, ExpnData, ExpnKind, LocalExpnId, MacroKind}; use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym}; @@ -742,60 +740,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { feature_err(&self.tcx.sess, sym::custom_inner_attributes, path.span, msg).emit(); } - const DIAGNOSTIC_ATTRIBUTES: &[(Symbol, Option)] = &[ - (sym::on_unimplemented, None), - (sym::do_not_recommend, None), - (sym::on_move, Some(sym::diagnostic_on_move)), - (sym::on_const, Some(sym::diagnostic_on_const)), - (sym::on_unknown, Some(sym::diagnostic_on_unknown)), - (sym::on_unmatched_args, Some(sym::diagnostic_on_unmatched_args)), - (sym::on_type_error, Some(sym::diagnostic_on_type_error)), - (sym::opaque, Some(sym::diagnostic_opaque)), - ]; - - if res == Res::NonMacroAttr(NonMacroAttrKind::Tool) - && let [namespace, attribute, ..] = &*path.segments - && namespace.ident.name == sym::diagnostic - && !DIAGNOSTIC_ATTRIBUTES.iter().any(|(attr, feature)| { - attribute.ident.name == *attr && feature.is_none_or(|f| self.features.enabled(f)) - }) - { - let name = attribute.ident.name; - let span = attribute.span(); - - let help = 'help: { - if self.tcx.sess.is_nightly_build() { - for (attr, feature) in DIAGNOSTIC_ATTRIBUTES { - if let Some(feature) = *feature - && *attr == name - { - break 'help Some( - diagnostics::UnknownDiagnosticAttributeHelp::UseFeature { feature }, - ); - } - } - } - - let candidates = DIAGNOSTIC_ATTRIBUTES - .iter() - .filter_map(|(attr, feature)| { - feature.is_none_or(|f| self.features.enabled(f)).then_some(*attr) - }) - .collect::>(); - - find_best_match_for_name(&candidates, name, None).map(|typo_name| { - diagnostics::UnknownDiagnosticAttributeHelp::Typo { span, typo_name } - }) - }; - - self.tcx.sess.psess.buffer_lint( - UNKNOWN_DIAGNOSTIC_ATTRIBUTES, - span, - node_id, - diagnostics::UnknownDiagnosticAttribute { help }, - ); - } - Ok((ext, res)) } diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-move.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-move.stderr index 593120edd1700..fa42547a6b652 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-on-move.stderr +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-move.stderr @@ -1,9 +1,10 @@ warning: unknown diagnostic attribute - --> $DIR/feature-gate-diagnostic-on-move.rs:5:15 + --> $DIR/feature-gate-diagnostic-on-move.rs:5:1 | LL | #[diagnostic::on_move(message = "Foo")] - | ^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = note: this is an experimental diagnostic attribute = help: add `#![feature(diagnostic_on_move)]` to the crate attributes to enable = note: `#[warn(unknown_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error-malformed-args.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error-malformed-args.stderr index c8b4aac78d6e1..3964152f3d4cf 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error-malformed-args.stderr +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error-malformed-args.stderr @@ -1,9 +1,10 @@ warning: unknown diagnostic attribute - --> $DIR/feature-gate-diagnostic-on-type-error-malformed-args.rs:5:15 + --> $DIR/feature-gate-diagnostic-on-type-error-malformed-args.rs:5:1 | LL | #[diagnostic::on_type_error(unknown = "")] - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = note: this is an experimental diagnostic attribute = help: add `#![feature(diagnostic_on_type_error)]` to the crate attributes to enable = note: `#[warn(unknown_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error.rs b/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error.rs index b17e6b57ef7da..355af18939e83 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error.rs +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error.rs @@ -2,6 +2,7 @@ #[diagnostic::on_type_error(note = "custom on_type_error note: expected {Expected}, found {Found}")] //~^ WARN unknown diagnostic attribute +//~| NOTE this is an experimental diagnostic attribute //~| NOTE `#[warn(unknown_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default #[derive(Debug)] struct Foo(T); diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error.stderr index 72f5cd932124f..f58f379aa13d0 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error.stderr +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-type-error.stderr @@ -1,14 +1,15 @@ warning: unknown diagnostic attribute - --> $DIR/feature-gate-diagnostic-on-type-error.rs:3:15 + --> $DIR/feature-gate-diagnostic-on-type-error.rs:3:1 | LL | #[diagnostic::on_type_error(note = "custom on_type_error note: expected {Expected}, found {Found}")] - | ^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = note: this is an experimental diagnostic attribute = help: add `#![feature(diagnostic_on_type_error)]` to the crate attributes to enable = note: `#[warn(unknown_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default error[E0308]: mismatched types - --> $DIR/feature-gate-diagnostic-on-type-error.rs:14:15 + --> $DIR/feature-gate-diagnostic-on-type-error.rs:15:15 | LL | takes_foo(foo); | --------- ^^^ expected `Foo`, found `Foo` @@ -18,7 +19,7 @@ LL | takes_foo(foo); = note: expected struct `Foo` found struct `Foo` note: function defined here - --> $DIR/feature-gate-diagnostic-on-type-error.rs:9:4 + --> $DIR/feature-gate-diagnostic-on-type-error.rs:10:4 | LL | fn takes_foo(_: Foo) {} | ^^^^^^^^^ ----------- diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.stderr index 6e9d35a09821b..d9a22ccf9eaa6 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.stderr +++ b/tests/ui/feature-gates/feature-gate-diagnostic-on-unknown.stderr @@ -7,11 +7,12 @@ LL | use std::vec::NotExisting; | no `NotExisting` in `vec` error: unknown diagnostic attribute - --> $DIR/feature-gate-diagnostic-on-unknown.rs:3:15 + --> $DIR/feature-gate-diagnostic-on-unknown.rs:3:1 | LL | #[diagnostic::on_unknown(message = "Tada")] - | ^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | + = note: this is an experimental diagnostic attribute = help: add `#![feature(diagnostic_on_unknown)]` to the crate attributes to enable note: the lint level is defined here --> $DIR/feature-gate-diagnostic-on-unknown.rs:1:9 diff --git a/tests/ui/feature-gates/feature-gate-diagnostic-opaque.stderr b/tests/ui/feature-gates/feature-gate-diagnostic-opaque.stderr index 90426a1324e81..3de2036717909 100644 --- a/tests/ui/feature-gates/feature-gate-diagnostic-opaque.stderr +++ b/tests/ui/feature-gates/feature-gate-diagnostic-opaque.stderr @@ -1,9 +1,10 @@ error: unknown diagnostic attribute - --> $DIR/feature-gate-diagnostic-opaque.rs:5:15 + --> $DIR/feature-gate-diagnostic-opaque.rs:5:1 | LL | #[diagnostic::opaque] - | ^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^ | + = note: this is an experimental diagnostic attribute = help: add `#![feature(diagnostic_opaque)]` to the crate attributes to enable note: the lint level is defined here --> $DIR/feature-gate-diagnostic-opaque.rs:3:9 @@ -12,11 +13,12 @@ LL | #![deny(unknown_diagnostic_attributes)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: unknown diagnostic attribute - --> $DIR/feature-gate-diagnostic-opaque.rs:11:15 + --> $DIR/feature-gate-diagnostic-opaque.rs:11:1 | LL | #[diagnostic::opaque] - | ^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^ | + = note: this is an experimental diagnostic attribute = help: add `#![feature(diagnostic_opaque)]` to the crate attributes to enable error: aborting due to 2 previous errors From a0c286ec076009516e24745824e878f70fb2a6e7 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Wed, 5 Aug 2026 19:19:32 +0300 Subject: [PATCH 045/100] [Priroda] render interpreter errors via InterpError::to_string --- src/tools/miri/priroda/src/debugger.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/tools/miri/priroda/src/debugger.rs b/src/tools/miri/priroda/src/debugger.rs index aca596dc3f18e..b2b7c8779709a 100644 --- a/src/tools/miri/priroda/src/debugger.rs +++ b/src/tools/miri/priroda/src/debugger.rs @@ -3,7 +3,7 @@ use std::ops::Range; use std::path::PathBuf; use miri::Immediate::Uninit; -use miri::{interpret, *}; +use miri::*; use rustc_abi::{FIRST_VARIANT, FieldIdx, Size}; use rustc_hir::def::CtorKind; use rustc_middle::mir::interpret::AllocId; @@ -640,7 +640,7 @@ impl<'tcx> PrirodaContext<'tcx> { Either::Left(mplace) => match self.render_mplace_bytes(&mplace).report_err() { Ok(bytes) => bytes, - Err(err) => format!("", interpret::format_interp_error(err)), + Err(err) => format!("", err.to_string()), }, } } @@ -809,9 +809,7 @@ impl<'tcx> PrirodaContext<'tcx> { .ecx .eval_place_to_op(*place, None) .map(|op| self.render_source_shaped_op(op)) - .unwrap_or_else(|err| { - format!("", interpret::format_interp_error(err)) - }); + .unwrap_or_else(|err| format!("", err.to_string())); local_descs.push(LocalDesc { source_name: Some(var_debug_info.name), From 195fd937366a09dfa4edd752183f24eb46574eba Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Wed, 5 Aug 2026 19:52:39 +0300 Subject: [PATCH 046/100] [Priroda] exhaustively list every DAP Command in dispatch and display List every Command variant in dispatch_request and display_command instead of a `_ =>` catch-all. New variants added upstream then fail to compile here instead of silently falling through the unsupported arm. --- src/tools/miri/priroda/src/frontend/dap.rs | 64 +++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index 838fd273b9a3c..a55f4a517a086 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -149,7 +149,36 @@ impl DapSession { } Command::Disconnect(_) => interp_ok(self.handle_disconnect(request).map(|()| DispatchOutcome::Exit)), - _ => + Command::Attach(_) + | Command::BreakpointLocations(_) + | Command::Cancel(_) + | Command::Completions(_) + | Command::DataBreakpointInfo(_) + | Command::Disassemble(_) + | Command::Evaluate(_) + | Command::ExceptionInfo(_) + | Command::Goto(_) + | Command::GotoTargets(_) + | Command::LoadedSources + | Command::Modules(_) + | Command::Pause(_) + | Command::ReadMemory(_) + | Command::Restart(_) + | Command::RestartFrame(_) + | Command::ReverseContinue(_) + | Command::SetDataBreakpoints(_) + | Command::SetExceptionBreakpoints(_) + | Command::SetExpression(_) + | Command::SetFunctionBreakpoints(_) + | Command::SetInstructionBreakpoints(_) + | Command::SetVariable(_) + | Command::Source(_) + | Command::StepBack(_) + | Command::StepInTargets(_) + | Command::StepOut(_) + | Command::Terminate(_) + | Command::TerminateThreads(_) + | Command::WriteMemory(_) => interp_ok( self.handle_unsupported_request(request).map(|()| DispatchOutcome::Continue), ), @@ -636,7 +665,38 @@ impl DapSession { Command::Next(_) => "next", Command::StepIn(_) => "stepIn", Command::Disconnect(_) => "disconnect", - _ => "unsupported", + Command::Attach(_) => "attach", + Command::BreakpointLocations(_) => "breakpointLocations", + Command::Cancel(_) => "cancel", + Command::Completions(_) => "completions", + Command::Continue(_) => "continue", + Command::DataBreakpointInfo(_) => "dataBreakpointInfo", + Command::Disassemble(_) => "disassemble", + Command::Evaluate(_) => "evaluate", + Command::ExceptionInfo(_) => "exceptionInfo", + Command::Goto(_) => "goto", + Command::GotoTargets(_) => "gotoTargets", + Command::LoadedSources => "loadedSources", + Command::Modules(_) => "modules", + Command::Pause(_) => "pause", + Command::ReadMemory(_) => "readMemory", + Command::Restart(_) => "restart", + Command::RestartFrame(_) => "restartFrame", + Command::ReverseContinue(_) => "reverseContinue", + Command::SetBreakpoints(_) => "setBreakpoints", + Command::SetDataBreakpoints(_) => "setDataBreakpoints", + Command::SetExceptionBreakpoints(_) => "setExceptionBreakpoints", + Command::SetExpression(_) => "setExpression", + Command::SetFunctionBreakpoints(_) => "setFunctionBreakpoints", + Command::SetInstructionBreakpoints(_) => "setInstructionBreakpoints", + Command::SetVariable(_) => "setVariable", + Command::Source(_) => "source", + Command::StepBack(_) => "stepBack", + Command::StepInTargets(_) => "stepInTargets", + Command::StepOut(_) => "stepOut", + Command::Terminate(_) => "terminate", + Command::TerminateThreads(_) => "terminateThreads", + Command::WriteMemory(_) => "writeMemory", } } From e0bf0c70255dae5ab33ceb0f6dd7787de18c683b Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Wed, 5 Aug 2026 19:54:02 +0300 Subject: [PATCH 047/100] [Priroda] route DAP request arguments into handlers, add setBreakpoints path guard Pull the inner arguments out of request.command at dispatch time and pass them by value into handle_scopes / handle_variables / handle_set_breakpoints, so the handlers no longer re-match on request.command. require_frame_id and require_variables_reference take the extracted value; the bug! fallback for the dispatch-only command in handle_variables is gone. Replace the inline DapState::Fresh check in dispatch_request with a require_initialized predicate, mirroring the other require_* guards. Reject setBreakpoints with an error when source.path is missing -- Priroda only resolves file-based breakpoints. --- src/tools/miri/priroda/src/frontend/dap.rs | 129 ++++++++++++--------- 1 file changed, 76 insertions(+), 53 deletions(-) diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index a55f4a517a086..63adf6ed9ddcc 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -1,6 +1,7 @@ use std::io::{self, BufReader, BufWriter}; use emmy_dap_types::prelude::events::{ExitedEventBody, StoppedEventBody}; +use emmy_dap_types::prelude::requests::SetBreakpointsArguments; use emmy_dap_types::prelude::responses::{ ContinueResponse, ScopesResponse, SetBreakpointsResponse, StackTraceResponse, ThreadsResponse, VariablesResponse, @@ -101,11 +102,12 @@ impl DapSession { request: Request, session: &mut PrirodaContext<'tcx>, ) -> InterpResult<'tcx, ServerResult> { - if self.state == DapState::Fresh && !matches!(&request.command, Command::Initialize(_)) { - return interp_ok( - self.respond_error(request, "initialize must be sent first") - .map(|()| DispatchOutcome::Continue), - ); + let uninitialized = match self.require_initialized(&request) { + Ok(rejected) => rejected, + Err(err) => return interp_ok(Err(err)), + }; + if uninitialized { + return interp_ok(Ok(DispatchOutcome::Continue)); } match &request.command { @@ -123,21 +125,31 @@ impl DapSession { interp_ok( self.handle_stack_trace(request, session).map(|()| DispatchOutcome::Continue), ), - Command::Scopes(_) => - interp_ok(self.handle_scopes(request, session).map(|()| DispatchOutcome::Continue)), - Command::Variables(_) => + Command::Scopes(args) => { + let frame_id = args.frame_id; interp_ok( - self.handle_variables(request, session).map(|()| DispatchOutcome::Continue), - ), + self.handle_scopes(request, frame_id, session) + .map(|()| DispatchOutcome::Continue), + ) + } + Command::Variables(args) => { + let variables_reference = args.variables_reference; + interp_ok( + self.handle_variables(request, variables_reference, session) + .map(|()| DispatchOutcome::Continue), + ) + } Command::Continue(_) => { let res = self.handle_continue(request, session)?; interp_ok(res.map(|()| self.dispatch_outcome())) } - Command::SetBreakpoints(_) => + Command::SetBreakpoints(args) => { + let args = args.clone(); interp_ok( - self.handle_set_breakpoints(request, session) + self.handle_set_breakpoints(request, &args, session) .map(|()| DispatchOutcome::Continue), - ), + ) + } Command::Next(_) | Command::StepIn(_) => { let body = match &request.command { Command::Next(_) => ResponseBody::Next, @@ -202,11 +214,12 @@ impl DapSession { fn handle_scopes<'tcx>( &mut self, request: Request, - _session: &PrirodaContext<'tcx>, + frame_id: i64, + session: &PrirodaContext<'tcx>, ) -> ServerResult { if self.reject_after_termination(&request)? || self.require_stopped(&request)? - || self.require_frame_id(&request)? + || self.require_frame_id(&request, frame_id)? { return Ok(()); } @@ -232,19 +245,20 @@ impl DapSession { fn handle_variables<'tcx>( &mut self, request: Request, + variables_reference: i64, session: &PrirodaContext<'tcx>, ) -> ServerResult { if self.reject_after_termination(&request)? || self.require_stopped(&request)? - || self.require_variables_reference(&request)? + || self.require_variables_reference(&request, variables_reference)? { return Ok(()); } - let variables = match &request.command { - Command::Variables(_) => - session.list_locals().into_iter().map(Self::local_to_variable).collect(), - _ => bug!("dispatch routes only Variables to handle_variables"), + let variables = if variables_reference == LOCALS_VARIABLES_REFERENCE { + session.list_locals().into_iter().map(Self::local_to_variable).collect() + } else { + Vec::new() }; let response = request.success(ResponseBody::Variables(VariablesResponse { variables })); @@ -430,34 +444,38 @@ impl DapSession { fn handle_set_breakpoints<'tcx>( &mut self, request: Request, + args: &SetBreakpointsArguments, session: &mut PrirodaContext<'tcx>, ) -> ServerResult { if self.reject_after_termination(&request)? { return Ok(()); } + let Some(ref path_str) = args.source.path else { + return self.respond_error( + request, + "setBreakpoints requires a source.path; sourceReference loads are not supported", + ); + }; + + let path = std::path::PathBuf::from(path_str); let mut breakpoints = Vec::new(); - if let Command::SetBreakpoints(ref args) = request.command { - if let Some(ref path_str) = args.source.path { - let path = std::path::PathBuf::from(path_str); - if let Some(ref req_bps) = args.breakpoints { - for req_bp in req_bps { - let line = req_bp.line as usize; - session.set_breakpoint(path.clone(), line); - breakpoints.push(DapBreakpoint { - verified: true, - message: None, - source: Some(args.source.clone()), - line: Some(req_bp.line), - column: req_bp.column, - end_line: None, - end_column: None, - id: None, - instruction_reference: None, - offset: None, - }); - } - } + if let Some(ref req_bps) = args.breakpoints { + for req_bp in req_bps { + let line = req_bp.line as usize; + session.set_breakpoint(path.clone(), line); + breakpoints.push(DapBreakpoint { + verified: true, + message: None, + source: Some(args.source.clone()), + line: Some(req_bp.line), + column: req_bp.column, + end_line: None, + end_column: None, + id: None, + instruction_reference: None, + offset: None, + }); } } @@ -504,6 +522,15 @@ impl DapSession { Ok(false) } + fn require_initialized(&mut self, request: &Request) -> ServerResult { + if self.state == DapState::Fresh && !matches!(&request.command, Command::Initialize(_)) { + self.server.respond(request.clone().error("initialize must be sent first"))?; + return Ok(true); + } + + Ok(false) + } + fn require_stopped(&mut self, request: &Request) -> ServerResult { if self.state != DapState::Stopped { self.server.respond(request.clone().error("request requires a stopped frame"))?; @@ -530,12 +557,8 @@ impl DapSession { Ok(false) } - fn require_frame_id(&mut self, request: &Request) -> ServerResult { - let Command::Scopes(args) = &request.command else { - bug!("dispatch routes only scopes to require_frame_id"); - }; - - if args.frame_id != STACK_FRAME_ID { + fn require_frame_id(&mut self, request: &Request, frame_id: i64) -> ServerResult { + if frame_id != STACK_FRAME_ID { self.server.respond(request.clone().error("unknown frameId"))?; return Ok(true); } @@ -543,12 +566,12 @@ impl DapSession { Ok(false) } - fn require_variables_reference(&mut self, request: &Request) -> ServerResult { - let Command::Variables(args) = &request.command else { - bug!("dispatch routes only variables to require_variables_reference"); - }; - - if args.variables_reference != LOCALS_VARIABLES_REFERENCE { + fn require_variables_reference( + &mut self, + request: &Request, + variables_reference: i64, + ) -> ServerResult { + if variables_reference != LOCALS_VARIABLES_REFERENCE { self.server.respond(request.clone().error("unknown variablesReference"))?; return Ok(true); } From b723b1276d90b409e5dd5cf4c591aff740ec4f10 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Wed, 5 Aug 2026 19:54:15 +0300 Subject: [PATCH 048/100] [Priroda] fill DAP Locals scope source position from current frame The Locals scope carried no source/line/column, so the editor could not anchor the variables view to the stopped frame. Pull them from session.current_location when present and bless the dap_scopes_variables* fixtures to the new fields. --- src/tools/miri/priroda/src/frontend/dap.rs | 29 +++++++++++++++++-- .../tests/ui/dap_scopes_variables.stdout | 2 +- .../tests/ui/dap_scopes_variables_next.stdout | 4 +-- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index 63adf6ed9ddcc..ec2f26e699f17 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -224,6 +224,29 @@ impl DapSession { return Ok(()); } + let (source, line, column) = match &session.current_location { + Some(location) => { + let source = session.local_path(location).as_ref().map(|path| { + Source { + name: path.file_name().map(|name| name.to_string_lossy().into_owned()), + path: Some(path.display().to_string()), + source_reference: Some(0), + presentation_hint: None, + origin: None, + sources: None, + checksums: None, + } + }); + let line = + location.line.try_into().unwrap_or_else(|_| bug!("source line exceeds i64")); + let column = location + .column + .try_into() + .unwrap_or_else(|_| bug!("source column exceeds i64")); + (source, Some(line), Some(column)) + } + None => (None, None, None), + }; let response = request.success(ResponseBody::Scopes(ScopesResponse { scopes: vec![Scope { name: "Locals".to_string(), @@ -232,9 +255,9 @@ impl DapSession { named_variables: None, indexed_variables: Some(0), expensive: false, - source: None, - line: None, - column: None, + source, + line, + column, end_line: None, end_column: None, }], diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout index 70c0765611115..4cc848bc88369 100644 --- a/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables.stdout @@ -12,6 +12,6 @@ Content-Length: {CONTENT_LENGTH} {"seq":6,"type":"response","request_seq":4,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables.rs","sourceReference":0},"line":4,"column":9}],"totalFrames":1},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":7,"type":"response","request_seq":5,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false}]},"error":null}Content-Length: {CONTENT_LENGTH} +{"seq":7,"type":"response","request_seq":5,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false,"source":{"name":"dap_scopes_variables.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables.rs","sourceReference":0},"line":4,"column":9}]},"error":null}Content-Length: {CONTENT_LENGTH} {"seq":8,"type":"response","request_seq":6,"success":true,"command":"variables","body":{"variables":[{"name":"_0","value":"","type":"()","variablesReference":0},{"name":"x","value":"","type":"i32","variablesReference":0},{"name":"y","value":"","type":"bool","variablesReference":0},{"name":"_3","value":"","type":"(i32, bool)","variablesReference":0},{"name":"_4","value":"","type":"i32","variablesReference":0},{"name":"_5","value":"","type":"bool","variablesReference":0}]},"error":null} \ No newline at end of file diff --git a/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout index 68e2e00bd74db..558af9b383840 100644 --- a/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_scopes_variables_next.stdout @@ -12,7 +12,7 @@ Content-Length: {CONTENT_LENGTH} {"seq":6,"type":"response","request_seq":4,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs","sourceReference":0},"line":4,"column":9}],"totalFrames":1},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":7,"type":"response","request_seq":5,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false}]},"error":null}Content-Length: {CONTENT_LENGTH} +{"seq":7,"type":"response","request_seq":5,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false,"source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs","sourceReference":0},"line":4,"column":9}]},"error":null}Content-Length: {CONTENT_LENGTH} {"seq":8,"type":"response","request_seq":6,"success":true,"command":"variables","body":{"variables":[{"name":"_0","value":"","type":"()","variablesReference":0},{"name":"x","value":"","type":"i32","variablesReference":0},{"name":"y","value":"","type":"bool","variablesReference":0},{"name":"_3","value":"","type":"(i32, bool)","variablesReference":0},{"name":"_4","value":"","type":"i32","variablesReference":0},{"name":"_5","value":"","type":"bool","variablesReference":0}]},"error":null}Content-Length: {CONTENT_LENGTH} @@ -22,7 +22,7 @@ Content-Length: {CONTENT_LENGTH} {"seq":11,"type":"response","request_seq":8,"success":true,"command":"stackTrace","body":{"stackFrames":[{"id":1,"name":"main","source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs","sourceReference":0},"line":5,"column":9}],"totalFrames":1},"error":null}Content-Length: {CONTENT_LENGTH} -{"seq":12,"type":"response","request_seq":9,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false}]},"error":null}Content-Length: {CONTENT_LENGTH} +{"seq":12,"type":"response","request_seq":9,"success":true,"command":"scopes","body":{"scopes":[{"name":"Locals","presentationHint":"locals","variablesReference":1,"indexedVariables":0,"expensive":false,"source":{"name":"dap_scopes_variables_next.rs","path":"{MANIFEST_DIR}/tests/ui/dap_scopes_variables_next.rs","sourceReference":0},"line":5,"column":9}]},"error":null}Content-Length: {CONTENT_LENGTH} {"seq":13,"type":"response","request_seq":10,"success":true,"command":"variables","body":{"variables":[{"name":"_0","value":"","type":"()","variablesReference":0},{"name":"x","value":"1_i32","type":"i32","variablesReference":0},{"name":"y","value":"","type":"bool","variablesReference":0},{"name":"_3","value":"","type":"(i32, bool)","variablesReference":0},{"name":"_4","value":"","type":"i32","variablesReference":0},{"name":"_5","value":"","type":"bool","variablesReference":0}]},"error":null}Content-Length: {CONTENT_LENGTH} From 66470f594ef414400f68f4dde6419ffdefada9f6 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Wed, 5 Aug 2026 18:03:13 +0300 Subject: [PATCH 049/100] [Priroda] rework DAP dispatch return type for bubble-up rejections Convert every require_* and reject_after_termination predicate from ServerResult eager-respond to pure Result<(), &str>, add DispatchOutcome::Rejected(&str), and change handlers to return Result. Once predicates stop eagerly responding, Rejected carries their errors out -- and vice versa. dispatch_request return type becomes InterpResult>. run_requests clones the request before dispatch so the original stays available for request.error(msg) when a Rejected bubbles up. Handlers rebuilt to if let Err(msg) = ...{ return Ok(Rejected(msg)); } + Ok(DispatchOutcome::Continue) endings; and_then chains in the execution handlers map to DispatchOutcome::Continue, and respond_error is gone from the happy path. --- src/tools/miri/priroda/src/frontend/dap.rs | 381 ++++++++++----------- 1 file changed, 173 insertions(+), 208 deletions(-) diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index ec2f26e699f17..847674a7cfae8 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -1,5 +1,6 @@ use std::io::{self, BufReader, BufWriter}; +use emmy_dap_types::errors::ServerError; use emmy_dap_types::prelude::events::{ExitedEventBody, StoppedEventBody}; use emmy_dap_types::prelude::requests::SetBreakpointsArguments; use emmy_dap_types::prelude::responses::{ @@ -25,6 +26,7 @@ type ServerResult = Result; enum DispatchOutcome { Continue, Exit, + Rejected(&'static str), } #[derive(Clone, Copy, PartialEq, Eq)] @@ -89,9 +91,17 @@ impl DapSession { Err(err) => return interp_ok(Err(err)), }; - match self.dispatch_request(request, session)? { + let request_for_dispatch = request.clone(); + + match self.dispatch_request(request_for_dispatch, session)? { Ok(DispatchOutcome::Continue) => {} Ok(DispatchOutcome::Exit) => return interp_ok(Ok(())), + Ok(DispatchOutcome::Rejected(msg)) => { + let response = request.error(msg); + if let Err(err) = self.server.respond(response) { + return interp_ok(Err(err)); + } + } Err(err) => return interp_ok(Err(err)), } } @@ -101,54 +111,29 @@ impl DapSession { &mut self, request: Request, session: &mut PrirodaContext<'tcx>, - ) -> InterpResult<'tcx, ServerResult> { - let uninitialized = match self.require_initialized(&request) { - Ok(rejected) => rejected, - Err(err) => return interp_ok(Err(err)), - }; - if uninitialized { - return interp_ok(Ok(DispatchOutcome::Continue)); - } - - match &request.command { - Command::Initialize(_) => - interp_ok(self.handle_initialize(request).map(|()| DispatchOutcome::Continue)), - Command::Launch(_) => - interp_ok(self.handle_launch(request).map(|()| DispatchOutcome::Continue)), - Command::ConfigurationDone => { - let res = self.handle_configuration_done(request, session)?; - interp_ok(res.map(|()| self.dispatch_outcome())) - } - Command::Threads => - interp_ok(self.handle_threads(request).map(|()| DispatchOutcome::Continue)), - Command::StackTrace(_) => - interp_ok( - self.handle_stack_trace(request, session).map(|()| DispatchOutcome::Continue), - ), + ) -> InterpResult<'tcx, Result> { + if let Err(msg) = self.require_initialized(&request) { + return interp_ok(Ok(DispatchOutcome::Rejected(msg))); + } + + let outcome = match &request.command { + Command::Initialize(_) => self.handle_initialize(request), + Command::Launch(_) => self.handle_launch(request), + Command::ConfigurationDone => return self.handle_configuration_done(request, session), + Command::Threads => self.handle_threads(request), + Command::StackTrace(_) => self.handle_stack_trace(request, session), Command::Scopes(args) => { let frame_id = args.frame_id; - interp_ok( - self.handle_scopes(request, frame_id, session) - .map(|()| DispatchOutcome::Continue), - ) + self.handle_scopes(request, frame_id, session) } Command::Variables(args) => { let variables_reference = args.variables_reference; - interp_ok( - self.handle_variables(request, variables_reference, session) - .map(|()| DispatchOutcome::Continue), - ) - } - Command::Continue(_) => { - let res = self.handle_continue(request, session)?; - interp_ok(res.map(|()| self.dispatch_outcome())) + self.handle_variables(request, variables_reference, session) } + Command::Continue(_) => return self.handle_continue(request, session), Command::SetBreakpoints(args) => { let args = args.clone(); - interp_ok( - self.handle_set_breakpoints(request, &args, session) - .map(|()| DispatchOutcome::Continue), - ) + self.handle_set_breakpoints(request, &args, session) } Command::Next(_) | Command::StepIn(_) => { let body = match &request.command { @@ -156,11 +141,9 @@ impl DapSession { Command::StepIn(_) => ResponseBody::StepIn, _ => bug!("step body is selected by the outer Next/StepIn match"), }; - let res = self.handle_step(request, body, session)?; - interp_ok(res.map(|()| self.dispatch_outcome())) + return self.handle_step(request, body, session); } - Command::Disconnect(_) => - interp_ok(self.handle_disconnect(request).map(|()| DispatchOutcome::Exit)), + Command::Disconnect(_) => self.handle_disconnect(request), Command::Attach(_) | Command::BreakpointLocations(_) | Command::Cancel(_) @@ -190,25 +173,21 @@ impl DapSession { | Command::StepOut(_) | Command::Terminate(_) | Command::TerminateThreads(_) - | Command::WriteMemory(_) => - interp_ok( - self.handle_unsupported_request(request).map(|()| DispatchOutcome::Continue), - ), - } + | Command::WriteMemory(_) => self.handle_unsupported_request(request), + }; + interp_ok(outcome) } /// FIXME: connect launch arguments to Priroda's session model. - fn handle_launch(&mut self, request: Request) -> ServerResult { - if self.reject_after_termination(&request)? - || self.require_state(&request, DapState::Initialized, "launch requires initialize")? - { - return Ok(()); + fn handle_launch(&mut self, request: Request) -> Result { + if let Err(msg) = self.require_state(DapState::Initialized) { + return Ok(DispatchOutcome::Rejected(msg)); } let response = request.success(ResponseBody::Launch); self.server.respond(response)?; self.state = DapState::Launched; - Ok(()) + Ok(DispatchOutcome::Continue) } fn handle_scopes<'tcx>( @@ -216,12 +195,12 @@ impl DapSession { request: Request, frame_id: i64, session: &PrirodaContext<'tcx>, - ) -> ServerResult { - if self.reject_after_termination(&request)? - || self.require_stopped(&request)? - || self.require_frame_id(&request, frame_id)? - { - return Ok(()); + ) -> Result { + if let Err(msg) = self.require_stopped() { + return Ok(DispatchOutcome::Rejected(msg)); + } + if let Err(msg) = Self::require_frame_id(frame_id) { + return Ok(DispatchOutcome::Rejected(msg)); } let (source, line, column) = match &session.current_location { @@ -262,7 +241,8 @@ impl DapSession { end_column: None, }], })); - self.server.respond(response) + self.server.respond(response)?; + Ok(DispatchOutcome::Continue) } fn handle_variables<'tcx>( @@ -270,12 +250,12 @@ impl DapSession { request: Request, variables_reference: i64, session: &PrirodaContext<'tcx>, - ) -> ServerResult { - if self.reject_after_termination(&request)? - || self.require_stopped(&request)? - || self.require_variables_reference(&request, variables_reference)? - { - return Ok(()); + ) -> Result { + if let Err(msg) = self.require_stopped() { + return Ok(DispatchOutcome::Rejected(msg)); + } + if let Err(msg) = Self::require_variables_reference(variables_reference) { + return Ok(DispatchOutcome::Rejected(msg)); } let variables = if variables_reference == LOCALS_VARIABLES_REFERENCE { @@ -285,29 +265,33 @@ impl DapSession { }; let response = request.success(ResponseBody::Variables(VariablesResponse { variables })); - self.server.respond(response) + self.server.respond(response)?; + Ok(DispatchOutcome::Continue) } fn handle_configuration_done<'tcx>( &mut self, request: Request, session: &mut PrirodaContext<'tcx>, - ) -> InterpResult<'tcx, ServerResult> { - let rejected = match self.check_configuration_done_request(&request) { - Ok(rejected) => rejected, + ) -> InterpResult<'tcx, Result> { + match self.check_configuration_done_request() { + Ok(DispatchOutcome::Continue) => {} + Ok(other) => return interp_ok(Ok(other)), Err(err) => return interp_ok(Err(err)), - }; - if rejected { - return interp_ok(Ok(())); } match Self::execution_outcome(session.stop_at_first_user_location()) { ExecutionOutcome::Stopped(_) => { let response = request.success(ResponseBody::ConfigurationDone); - interp_ok(self.server.respond(response).and_then(|()| { - self.state = DapState::Stopped; - self.send_stopped_event(StoppedEventReason::Entry) - })) + interp_ok( + self.server + .respond(response) + .and_then(|()| { + self.state = DapState::Stopped; + self.send_stopped_event(StoppedEventReason::Entry) + }) + .map(|()| DispatchOutcome::Continue), + ) } ExecutionOutcome::Terminated { code } => interp_ok(self.respond_terminated(request, ResponseBody::ConfigurationDone, code)), @@ -318,15 +302,16 @@ impl DapSession { /// FIXME: replace this with Miri thread state once Priroda exposes a /// frontend-facing thread model. - fn handle_threads(&mut self, request: Request) -> ServerResult { - if self.reject_after_termination(&request)? { - return Ok(()); + fn handle_threads(&mut self, request: Request) -> Result { + if let Err(msg) = self.reject_after_termination() { + return Ok(DispatchOutcome::Rejected(msg)); } let response = request.success(ResponseBody::Threads(ThreadsResponse { threads: vec![Thread { id: THREAD_ID, name: "main".to_string() }], })); - self.server.respond(response) + self.server.respond(response)?; + Ok(DispatchOutcome::Continue) } /// FIXME: report all frames once Priroda exposes a frontend-facing stack model. @@ -334,12 +319,12 @@ impl DapSession { &mut self, request: Request, session: &PrirodaContext<'tcx>, - ) -> ServerResult { - if self.reject_after_termination(&request)? - || self.require_stopped(&request)? - || self.require_thread_id(&request)? - { - return Ok(()); + ) -> Result { + if let Err(msg) = self.require_stopped() { + return Ok(DispatchOutcome::Rejected(msg)); + } + if let Err(msg) = Self::require_thread_id(&request) { + return Ok(DispatchOutcome::Rejected(msg)); } let stack_frames = match &session.current_location { @@ -383,18 +368,14 @@ impl DapSession { stack_frames, total_frames: Some(total_frames), })); - self.server.respond(response) + self.server.respond(response)?; + Ok(DispatchOutcome::Continue) } /// FIXME: grow capabilities as Priroda adds DAP features. - fn handle_initialize(&mut self, request: Request) -> ServerResult { - // Advertise configurationDone support ahead of its handler so VS Code - // completes the full handshake; the handler arrives in a later commit. - if self.reject_after_termination(&request)? { - return Ok(()); - } + fn handle_initialize(&mut self, request: Request) -> Result { if self.state != DapState::Fresh { - return self.respond_error(request, "initialize may only be sent once"); + return Ok(DispatchOutcome::Rejected("initialize may only be sent once")); } let response = request.success(ResponseBody::Initialize(Capabilities { @@ -405,7 +386,7 @@ impl DapSession { self.server.respond(response)?; self.server.send_event(Event::Initialized)?; self.state = DapState::Initialized; - Ok(()) + Ok(DispatchOutcome::Continue) } /// FIXME: distinguish step-over from step-in once Priroda has call-aware stepping. @@ -414,21 +395,24 @@ impl DapSession { request: Request, body: ResponseBody, session: &mut PrirodaContext<'tcx>, - ) -> InterpResult<'tcx, ServerResult> { - let rejected = match self.check_step_request(&request) { - Ok(rejected) => rejected, + ) -> InterpResult<'tcx, Result> { + match self.check_step_request(&request) { + Ok(DispatchOutcome::Continue) => {} + Ok(other) => return interp_ok(Ok(other)), Err(err) => return interp_ok(Err(err)), - }; - if rejected { - return interp_ok(Ok(())); } match Self::execution_outcome(session.step()) { ExecutionOutcome::Stopped(result) => - interp_ok(self.server.respond(request.success(body)).and_then(|()| { - self.state = DapState::Stopped; - self.send_stopped_event(Self::stopped_reason(result)) - })), + interp_ok( + self.server + .respond(request.success(body)) + .and_then(|()| { + self.state = DapState::Stopped; + self.send_stopped_event(Self::stopped_reason(result)) + }) + .map(|()| DispatchOutcome::Continue), + ), ExecutionOutcome::Terminated { code } => interp_ok(self.respond_terminated(request, body, code)), ExecutionOutcome::Failed(message) => @@ -440,23 +424,26 @@ impl DapSession { &mut self, request: Request, session: &mut PrirodaContext<'tcx>, - ) -> InterpResult<'tcx, ServerResult> { - let rejected = match self.check_step_request(&request) { - Ok(rejected) => rejected, + ) -> InterpResult<'tcx, Result> { + match self.check_step_request(&request) { + Ok(DispatchOutcome::Continue) => {} + Ok(other) => return interp_ok(Ok(other)), Err(err) => return interp_ok(Err(err)), - }; - if rejected { - return interp_ok(Ok(())); } let body = ResponseBody::Continue(ContinueResponse { all_threads_continued: Some(true) }); match Self::execution_outcome(session.continue_execution()) { ExecutionOutcome::Stopped(result) => - interp_ok(self.server.respond(request.success(body)).and_then(|()| { - self.state = DapState::Stopped; - self.send_stopped_event(Self::stopped_reason(result)) - })), + interp_ok( + self.server + .respond(request.success(body)) + .and_then(|()| { + self.state = DapState::Stopped; + self.send_stopped_event(Self::stopped_reason(result)) + }) + .map(|()| DispatchOutcome::Continue), + ), ExecutionOutcome::Terminated { code } => interp_ok(self.respond_terminated(request, body, code)), ExecutionOutcome::Failed(message) => @@ -469,16 +456,15 @@ impl DapSession { request: Request, args: &SetBreakpointsArguments, session: &mut PrirodaContext<'tcx>, - ) -> ServerResult { - if self.reject_after_termination(&request)? { - return Ok(()); + ) -> Result { + if let Err(msg) = self.reject_after_termination() { + return Ok(DispatchOutcome::Rejected(msg)); } let Some(ref path_str) = args.source.path else { - return self.respond_error( - request, + return Ok(DispatchOutcome::Rejected( "setBreakpoints requires a source.path; sourceReference loads are not supported", - ); + )); }; let path = std::path::PathBuf::from(path_str); @@ -504,66 +490,63 @@ impl DapSession { let response = request.success(ResponseBody::SetBreakpoints(SetBreakpointsResponse { breakpoints })); - self.server.respond(response) + self.server.respond(response)?; + Ok(DispatchOutcome::Continue) } - fn handle_disconnect(&mut self, request: Request) -> ServerResult { + fn handle_disconnect(&mut self, request: Request) -> Result { self.server.respond(request.success(ResponseBody::Disconnect))?; self.state = DapState::Terminated; - self.server.send_event(Event::Terminated(None)) + self.server.send_event(Event::Terminated(None))?; + Ok(DispatchOutcome::Exit) } - fn handle_unsupported_request(&mut self, request: Request) -> ServerResult { + fn handle_unsupported_request( + &mut self, + request: Request, + ) -> Result { let message = format!( "unsupported request in Priroda DAP demo mode: {}", Self::display_command(&request.command) ); let response = request.error(&message); - self.server.respond(response) + self.server.respond(response)?; + Ok(DispatchOutcome::Continue) } - fn reject_after_termination(&mut self, request: &Request) -> ServerResult { + fn reject_after_termination(&self) -> Result<(), &'static str> { if self.state == DapState::Terminated { - self.server.respond(request.clone().error("request received after termination"))?; - return Ok(true); + return Err("request received after termination"); } - - Ok(false) + Ok(()) } - fn require_state( - &mut self, - request: &Request, - expected: DapState, - message: &'static str, - ) -> ServerResult { + fn require_state(&self, expected: DapState) -> Result<(), &'static str> { if self.state != expected { - self.server.respond(request.clone().error(message))?; - return Ok(true); + return Err(match expected { + DapState::Initialized => "launch requires initialize", + DapState::Launched => "configurationDone requires launch", + _ => "invalid session state for request", + }); } - - Ok(false) + Ok(()) } - fn require_initialized(&mut self, request: &Request) -> ServerResult { + fn require_initialized(&self, request: &Request) -> Result<(), &'static str> { if self.state == DapState::Fresh && !matches!(&request.command, Command::Initialize(_)) { - self.server.respond(request.clone().error("initialize must be sent first"))?; - return Ok(true); + return Err("initialize must be sent first"); } - - Ok(false) + Ok(()) } - fn require_stopped(&mut self, request: &Request) -> ServerResult { + fn require_stopped(&self) -> Result<(), &'static str> { if self.state != DapState::Stopped { - self.server.respond(request.clone().error("request requires a stopped frame"))?; - return Ok(true); + return Err("request requires a stopped frame"); } - - Ok(false) + Ok(()) } - fn require_thread_id(&mut self, request: &Request) -> ServerResult { + fn require_thread_id(request: &Request) -> Result<(), &'static str> { let valid = match &request.command { Command::StackTrace(args) => args.thread_id == THREAD_ID, Command::Next(args) => args.thread_id == THREAD_ID, @@ -573,80 +556,62 @@ impl DapSession { }; if !valid { - self.server.respond(request.clone().error("unknown threadId"))?; - return Ok(true); + return Err("unknown threadId"); } - - Ok(false) + Ok(()) } - fn require_frame_id(&mut self, request: &Request, frame_id: i64) -> ServerResult { + fn require_frame_id(frame_id: i64) -> Result<(), &'static str> { if frame_id != STACK_FRAME_ID { - self.server.respond(request.clone().error("unknown frameId"))?; - return Ok(true); + return Err("unknown frameId"); } - - Ok(false) + Ok(()) } - fn require_variables_reference( - &mut self, - request: &Request, - variables_reference: i64, - ) -> ServerResult { + fn require_variables_reference(variables_reference: i64) -> Result<(), &'static str> { if variables_reference != LOCALS_VARIABLES_REFERENCE { - self.server.respond(request.clone().error("unknown variablesReference"))?; - return Ok(true); + return Err("unknown variablesReference"); } - - Ok(false) + Ok(()) } - fn check_configuration_done_request(&mut self, request: &Request) -> ServerResult { - if self.reject_after_termination(request)? { - return Ok(true); + fn check_configuration_done_request(&self) -> Result { + if let Err(msg) = self.reject_after_termination() { + return Ok(DispatchOutcome::Rejected(msg)); } - if self.state == DapState::Stopped { - self.server - .respond(request.clone().error("configurationDone may only be sent once"))?; - return Ok(true); + return Ok(DispatchOutcome::Rejected("configurationDone may only be sent once")); } - - if self.require_state(request, DapState::Launched, "configurationDone requires launch")? { - return Ok(true); + if let Err(msg) = self.require_state(DapState::Launched) { + return Ok(DispatchOutcome::Rejected(msg)); } - Ok(false) + Ok(DispatchOutcome::Continue) } - fn check_step_request(&mut self, request: &Request) -> ServerResult { - if self.reject_after_termination(request)? - || self.require_stopped(request)? - || self.require_thread_id(request)? - { - return Ok(true); + fn check_step_request(&self, request: &Request) -> Result { + if let Err(msg) = self.reject_after_termination() { + return Ok(DispatchOutcome::Rejected(msg)); } - - Ok(false) - } - - fn respond_error(&mut self, request: Request, message: &str) -> ServerResult { - self.server.respond(request.error(message)) - } - - fn dispatch_outcome(&self) -> DispatchOutcome { - if self.state == DapState::Terminated { - DispatchOutcome::Exit - } else { - DispatchOutcome::Continue + if let Err(msg) = self.require_stopped() { + return Ok(DispatchOutcome::Rejected(msg)); + } + if let Err(msg) = Self::require_thread_id(request) { + return Ok(DispatchOutcome::Rejected(msg)); } + + Ok(DispatchOutcome::Continue) } - fn respond_execution_error(&mut self, request: Request, message: String) -> ServerResult { + fn respond_execution_error( + &mut self, + request: Request, + message: String, + ) -> Result { self.state = DapState::Terminated; self.server.respond(request.error(&message))?; - self.server.send_event(Event::Terminated(None)) + self.server.send_event(Event::Terminated(None))?; + Ok(DispatchOutcome::Exit) } fn respond_terminated( @@ -654,12 +619,12 @@ impl DapSession { request: Request, body: ResponseBody, code: i32, - ) -> ServerResult { + ) -> Result { self.state = DapState::Terminated; self.server.respond(request.success(body))?; self.server.send_event(Event::Exited(ExitedEventBody { exit_code: code.into() }))?; self.server.send_event(Event::Terminated(None))?; - Ok(()) + Ok(DispatchOutcome::Exit) } fn execution_outcome<'tcx>(result: InterpResult<'tcx, StepResult>) -> ExecutionOutcome { From 223740293b6a8feb69239145e950dd8f3b3a8023 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Wed, 5 Aug 2026 18:03:13 +0300 Subject: [PATCH 050/100] [Priroda] drop redundant termination guards in DAP handlers A bunch of reject_after_termination calls sat before a state check that already excludes Terminated, so the reject was dead. Dropped those. check_configuration_done_request and check_step_request collapse to their actual predicate -- require_state(Launched) on the first, require_stopped + require_thread_id on the second. The"configurationDone may only be sent once" arm is gone since require_state(Launched) already rejects Stopped. Updated dap_rejects_repeated_configuration_done.stdout to the new "configurationDone requires launch" message. --- src/tools/miri/priroda/src/frontend/dap.rs | 9 --------- .../ui/dap_rejects_repeated_configuration_done.stdout | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index 847674a7cfae8..0098b6f8c7397 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -576,12 +576,6 @@ impl DapSession { } fn check_configuration_done_request(&self) -> Result { - if let Err(msg) = self.reject_after_termination() { - return Ok(DispatchOutcome::Rejected(msg)); - } - if self.state == DapState::Stopped { - return Ok(DispatchOutcome::Rejected("configurationDone may only be sent once")); - } if let Err(msg) = self.require_state(DapState::Launched) { return Ok(DispatchOutcome::Rejected(msg)); } @@ -590,9 +584,6 @@ impl DapSession { } fn check_step_request(&self, request: &Request) -> Result { - if let Err(msg) = self.reject_after_termination() { - return Ok(DispatchOutcome::Rejected(msg)); - } if let Err(msg) = self.require_stopped() { return Ok(DispatchOutcome::Rejected(msg)); } diff --git a/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout index c7d7b63bf5608..abc6e1cf7d694 100644 --- a/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout +++ b/src/tools/miri/priroda/tests/ui/dap_rejects_repeated_configuration_done.stdout @@ -10,7 +10,7 @@ Content-Length: {CONTENT_LENGTH} {"seq":5,"type":"event","event":"stopped","body":{"reason":"entry","description":null,"threadId":1,"preserveFocusHint":null,"text":null,"allThreadsStopped":true,"hitBreakpointIds":null}}Content-Length: {CONTENT_LENGTH} -{"seq":6,"type":"response","request_seq":4,"success":false,"message":"configurationDone may only be sent once","command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} +{"seq":6,"type":"response","request_seq":4,"success":false,"message":"configurationDone requires launch","command":"configurationDone","error":null}Content-Length: {CONTENT_LENGTH} {"seq":7,"type":"response","request_seq":5,"success":true,"command":"disconnect","error":null}Content-Length: {CONTENT_LENGTH} From 66e0a9eff7143ccf216483c9b0511a6915102286 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Wed, 5 Aug 2026 18:06:12 +0300 Subject: [PATCH 051/100] [Priroda] bubble predicate failures through HandlerError MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dropped DispatchOutcome::Rejected in favor of HandlerError, which has Reject and Transport variants. Predicates stay Result<(), &str>; callers do .map_err(HandlerError::Reject)?. With From for HandlerError, self.server.respond(..)? in handlers just works. run_requests now sends request.error(msg) for rejections and bubbles transport errors out — one send per request. This addresses the feedback about Result and predicates eagerly reporting inside the require methods. --- src/tools/miri/priroda/src/frontend/dap.rs | 168 +++++++++------------ 1 file changed, 72 insertions(+), 96 deletions(-) diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index 0098b6f8c7397..b110e90a4fb1e 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -23,10 +23,20 @@ const STACK_FRAME_ID: i64 = 1; const LOCALS_VARIABLES_REFERENCE: i64 = 1; type ServerResult = Result; -enum DispatchOutcome { +enum HandlerOutcome { Continue, Exit, - Rejected(&'static str), +} + +enum HandlerError { + Reject(&'static str), + Transport(ServerError), +} + +impl From for HandlerError { + fn from(e: ServerError) -> Self { + HandlerError::Transport(e) + } } #[derive(Clone, Copy, PartialEq, Eq)] @@ -94,15 +104,15 @@ impl DapSession { let request_for_dispatch = request.clone(); match self.dispatch_request(request_for_dispatch, session)? { - Ok(DispatchOutcome::Continue) => {} - Ok(DispatchOutcome::Exit) => return interp_ok(Ok(())), - Ok(DispatchOutcome::Rejected(msg)) => { + Ok(HandlerOutcome::Continue) => {} + Ok(HandlerOutcome::Exit) => return interp_ok(Ok(())), + Err(HandlerError::Reject(msg)) => { let response = request.error(msg); if let Err(err) = self.server.respond(response) { return interp_ok(Err(err)); } } - Err(err) => return interp_ok(Err(err)), + Err(HandlerError::Transport(e)) => return interp_ok(Err(e)), } } } @@ -111,9 +121,9 @@ impl DapSession { &mut self, request: Request, session: &mut PrirodaContext<'tcx>, - ) -> InterpResult<'tcx, Result> { + ) -> InterpResult<'tcx, Result> { if let Err(msg) = self.require_initialized(&request) { - return interp_ok(Ok(DispatchOutcome::Rejected(msg))); + return interp_ok(Err(HandlerError::Reject(msg))); } let outcome = match &request.command { @@ -179,15 +189,13 @@ impl DapSession { } /// FIXME: connect launch arguments to Priroda's session model. - fn handle_launch(&mut self, request: Request) -> Result { - if let Err(msg) = self.require_state(DapState::Initialized) { - return Ok(DispatchOutcome::Rejected(msg)); - } + fn handle_launch(&mut self, request: Request) -> Result { + self.require_state(DapState::Initialized).map_err(HandlerError::Reject)?; let response = request.success(ResponseBody::Launch); self.server.respond(response)?; self.state = DapState::Launched; - Ok(DispatchOutcome::Continue) + Ok(HandlerOutcome::Continue) } fn handle_scopes<'tcx>( @@ -195,13 +203,9 @@ impl DapSession { request: Request, frame_id: i64, session: &PrirodaContext<'tcx>, - ) -> Result { - if let Err(msg) = self.require_stopped() { - return Ok(DispatchOutcome::Rejected(msg)); - } - if let Err(msg) = Self::require_frame_id(frame_id) { - return Ok(DispatchOutcome::Rejected(msg)); - } + ) -> Result { + self.require_stopped().map_err(HandlerError::Reject)?; + Self::require_frame_id(frame_id).map_err(HandlerError::Reject)?; let (source, line, column) = match &session.current_location { Some(location) => { @@ -242,7 +246,7 @@ impl DapSession { }], })); self.server.respond(response)?; - Ok(DispatchOutcome::Continue) + Ok(HandlerOutcome::Continue) } fn handle_variables<'tcx>( @@ -250,13 +254,9 @@ impl DapSession { request: Request, variables_reference: i64, session: &PrirodaContext<'tcx>, - ) -> Result { - if let Err(msg) = self.require_stopped() { - return Ok(DispatchOutcome::Rejected(msg)); - } - if let Err(msg) = Self::require_variables_reference(variables_reference) { - return Ok(DispatchOutcome::Rejected(msg)); - } + ) -> Result { + self.require_stopped().map_err(HandlerError::Reject)?; + Self::require_variables_reference(variables_reference).map_err(HandlerError::Reject)?; let variables = if variables_reference == LOCALS_VARIABLES_REFERENCE { session.list_locals().into_iter().map(Self::local_to_variable).collect() @@ -266,18 +266,16 @@ impl DapSession { let response = request.success(ResponseBody::Variables(VariablesResponse { variables })); self.server.respond(response)?; - Ok(DispatchOutcome::Continue) + Ok(HandlerOutcome::Continue) } fn handle_configuration_done<'tcx>( &mut self, request: Request, session: &mut PrirodaContext<'tcx>, - ) -> InterpResult<'tcx, Result> { - match self.check_configuration_done_request() { - Ok(DispatchOutcome::Continue) => {} - Ok(other) => return interp_ok(Ok(other)), - Err(err) => return interp_ok(Err(err)), + ) -> InterpResult<'tcx, Result> { + if let Err(msg) = self.require_state(DapState::Launched) { + return interp_ok(Err(HandlerError::Reject(msg))); } match Self::execution_outcome(session.stop_at_first_user_location()) { @@ -290,7 +288,8 @@ impl DapSession { self.state = DapState::Stopped; self.send_stopped_event(StoppedEventReason::Entry) }) - .map(|()| DispatchOutcome::Continue), + .map(|()| HandlerOutcome::Continue) + .map_err(HandlerError::Transport), ) } ExecutionOutcome::Terminated { code } => @@ -302,16 +301,14 @@ impl DapSession { /// FIXME: replace this with Miri thread state once Priroda exposes a /// frontend-facing thread model. - fn handle_threads(&mut self, request: Request) -> Result { - if let Err(msg) = self.reject_after_termination() { - return Ok(DispatchOutcome::Rejected(msg)); - } + fn handle_threads(&mut self, request: Request) -> Result { + self.reject_after_termination().map_err(HandlerError::Reject)?; let response = request.success(ResponseBody::Threads(ThreadsResponse { threads: vec![Thread { id: THREAD_ID, name: "main".to_string() }], })); self.server.respond(response)?; - Ok(DispatchOutcome::Continue) + Ok(HandlerOutcome::Continue) } /// FIXME: report all frames once Priroda exposes a frontend-facing stack model. @@ -319,13 +316,9 @@ impl DapSession { &mut self, request: Request, session: &PrirodaContext<'tcx>, - ) -> Result { - if let Err(msg) = self.require_stopped() { - return Ok(DispatchOutcome::Rejected(msg)); - } - if let Err(msg) = Self::require_thread_id(&request) { - return Ok(DispatchOutcome::Rejected(msg)); - } + ) -> Result { + self.require_stopped().map_err(HandlerError::Reject)?; + Self::require_thread_id(&request).map_err(HandlerError::Reject)?; let stack_frames = match &session.current_location { Some(location) => { @@ -369,13 +362,13 @@ impl DapSession { total_frames: Some(total_frames), })); self.server.respond(response)?; - Ok(DispatchOutcome::Continue) + Ok(HandlerOutcome::Continue) } /// FIXME: grow capabilities as Priroda adds DAP features. - fn handle_initialize(&mut self, request: Request) -> Result { + fn handle_initialize(&mut self, request: Request) -> Result { if self.state != DapState::Fresh { - return Ok(DispatchOutcome::Rejected("initialize may only be sent once")); + return Err(HandlerError::Reject("initialize may only be sent once")); } let response = request.success(ResponseBody::Initialize(Capabilities { @@ -386,7 +379,7 @@ impl DapSession { self.server.respond(response)?; self.server.send_event(Event::Initialized)?; self.state = DapState::Initialized; - Ok(DispatchOutcome::Continue) + Ok(HandlerOutcome::Continue) } /// FIXME: distinguish step-over from step-in once Priroda has call-aware stepping. @@ -395,11 +388,12 @@ impl DapSession { request: Request, body: ResponseBody, session: &mut PrirodaContext<'tcx>, - ) -> InterpResult<'tcx, Result> { - match self.check_step_request(&request) { - Ok(DispatchOutcome::Continue) => {} - Ok(other) => return interp_ok(Ok(other)), - Err(err) => return interp_ok(Err(err)), + ) -> InterpResult<'tcx, Result> { + if let Err(msg) = self.require_stopped() { + return interp_ok(Err(HandlerError::Reject(msg))); + } + if let Err(msg) = Self::require_thread_id(&request) { + return interp_ok(Err(HandlerError::Reject(msg))); } match Self::execution_outcome(session.step()) { @@ -411,7 +405,8 @@ impl DapSession { self.state = DapState::Stopped; self.send_stopped_event(Self::stopped_reason(result)) }) - .map(|()| DispatchOutcome::Continue), + .map(|()| HandlerOutcome::Continue) + .map_err(HandlerError::Transport), ), ExecutionOutcome::Terminated { code } => interp_ok(self.respond_terminated(request, body, code)), @@ -424,11 +419,12 @@ impl DapSession { &mut self, request: Request, session: &mut PrirodaContext<'tcx>, - ) -> InterpResult<'tcx, Result> { - match self.check_step_request(&request) { - Ok(DispatchOutcome::Continue) => {} - Ok(other) => return interp_ok(Ok(other)), - Err(err) => return interp_ok(Err(err)), + ) -> InterpResult<'tcx, Result> { + if let Err(msg) = self.require_stopped() { + return interp_ok(Err(HandlerError::Reject(msg))); + } + if let Err(msg) = Self::require_thread_id(&request) { + return interp_ok(Err(HandlerError::Reject(msg))); } let body = ResponseBody::Continue(ContinueResponse { all_threads_continued: Some(true) }); @@ -442,7 +438,8 @@ impl DapSession { self.state = DapState::Stopped; self.send_stopped_event(Self::stopped_reason(result)) }) - .map(|()| DispatchOutcome::Continue), + .map(|()| HandlerOutcome::Continue) + .map_err(HandlerError::Transport), ), ExecutionOutcome::Terminated { code } => interp_ok(self.respond_terminated(request, body, code)), @@ -456,13 +453,11 @@ impl DapSession { request: Request, args: &SetBreakpointsArguments, session: &mut PrirodaContext<'tcx>, - ) -> Result { - if let Err(msg) = self.reject_after_termination() { - return Ok(DispatchOutcome::Rejected(msg)); - } + ) -> Result { + self.reject_after_termination().map_err(HandlerError::Reject)?; let Some(ref path_str) = args.source.path else { - return Ok(DispatchOutcome::Rejected( + return Err(HandlerError::Reject( "setBreakpoints requires a source.path; sourceReference loads are not supported", )); }; @@ -491,27 +486,27 @@ impl DapSession { let response = request.success(ResponseBody::SetBreakpoints(SetBreakpointsResponse { breakpoints })); self.server.respond(response)?; - Ok(DispatchOutcome::Continue) + Ok(HandlerOutcome::Continue) } - fn handle_disconnect(&mut self, request: Request) -> Result { + fn handle_disconnect(&mut self, request: Request) -> Result { self.server.respond(request.success(ResponseBody::Disconnect))?; self.state = DapState::Terminated; self.server.send_event(Event::Terminated(None))?; - Ok(DispatchOutcome::Exit) + Ok(HandlerOutcome::Exit) } fn handle_unsupported_request( &mut self, request: Request, - ) -> Result { + ) -> Result { let message = format!( "unsupported request in Priroda DAP demo mode: {}", Self::display_command(&request.command) ); let response = request.error(&message); self.server.respond(response)?; - Ok(DispatchOutcome::Continue) + Ok(HandlerOutcome::Continue) } fn reject_after_termination(&self) -> Result<(), &'static str> { @@ -575,34 +570,15 @@ impl DapSession { Ok(()) } - fn check_configuration_done_request(&self) -> Result { - if let Err(msg) = self.require_state(DapState::Launched) { - return Ok(DispatchOutcome::Rejected(msg)); - } - - Ok(DispatchOutcome::Continue) - } - - fn check_step_request(&self, request: &Request) -> Result { - if let Err(msg) = self.require_stopped() { - return Ok(DispatchOutcome::Rejected(msg)); - } - if let Err(msg) = Self::require_thread_id(request) { - return Ok(DispatchOutcome::Rejected(msg)); - } - - Ok(DispatchOutcome::Continue) - } - fn respond_execution_error( &mut self, request: Request, message: String, - ) -> Result { + ) -> Result { self.state = DapState::Terminated; self.server.respond(request.error(&message))?; self.server.send_event(Event::Terminated(None))?; - Ok(DispatchOutcome::Exit) + Ok(HandlerOutcome::Exit) } fn respond_terminated( @@ -610,12 +586,12 @@ impl DapSession { request: Request, body: ResponseBody, code: i32, - ) -> Result { + ) -> Result { self.state = DapState::Terminated; self.server.respond(request.success(body))?; self.server.send_event(Event::Exited(ExitedEventBody { exit_code: code.into() }))?; self.server.send_event(Event::Terminated(None))?; - Ok(DispatchOutcome::Exit) + Ok(HandlerOutcome::Exit) } fn execution_outcome<'tcx>(result: InterpResult<'tcx, StepResult>) -> ExecutionOutcome { From a17c6d56bf1947f0991901c1a8c6fce4af4adbba Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Wed, 5 Aug 2026 18:08:13 +0300 Subject: [PATCH 052/100] [Priroda] pass thread_id by value into require_thread_id require_thread_id now takes i64. Callers already know which command they are handling, so they pull thread_id directly. This was the last predicate that took &Request. Inlined require_initialized at its one callsite, single matches! check, no point keeping it separate. The dispatch extraction arms use bug!("wrong command") for the impossible fallback, matching the existing bug! style in the file. --- src/tools/miri/priroda/src/frontend/dap.rs | 42 +++++++++++----------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index b110e90a4fb1e..a44e462a16d66 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -122,8 +122,8 @@ impl DapSession { request: Request, session: &mut PrirodaContext<'tcx>, ) -> InterpResult<'tcx, Result> { - if let Err(msg) = self.require_initialized(&request) { - return interp_ok(Err(HandlerError::Reject(msg))); + if self.state == DapState::Fresh && !matches!(&request.command, Command::Initialize(_)) { + return interp_ok(Err(HandlerError::Reject("initialize must be sent first"))); } let outcome = match &request.command { @@ -317,8 +317,12 @@ impl DapSession { request: Request, session: &PrirodaContext<'tcx>, ) -> Result { + let thread_id = match &request.command { + Command::StackTrace(args) => args.thread_id, + _ => bug!("wrong command"), + }; self.require_stopped().map_err(HandlerError::Reject)?; - Self::require_thread_id(&request).map_err(HandlerError::Reject)?; + Self::require_thread_id(thread_id).map_err(HandlerError::Reject)?; let stack_frames = match &session.current_location { Some(location) => { @@ -392,7 +396,12 @@ impl DapSession { if let Err(msg) = self.require_stopped() { return interp_ok(Err(HandlerError::Reject(msg))); } - if let Err(msg) = Self::require_thread_id(&request) { + let thread_id = match &request.command { + Command::Next(args) => args.thread_id, + Command::StepIn(args) => args.thread_id, + _ => bug!("wrong command"), + }; + if let Err(msg) = Self::require_thread_id(thread_id) { return interp_ok(Err(HandlerError::Reject(msg))); } @@ -423,7 +432,11 @@ impl DapSession { if let Err(msg) = self.require_stopped() { return interp_ok(Err(HandlerError::Reject(msg))); } - if let Err(msg) = Self::require_thread_id(&request) { + let thread_id = match &request.command { + Command::Continue(args) => args.thread_id, + _ => bug!("wrong command"), + }; + if let Err(msg) = Self::require_thread_id(thread_id) { return interp_ok(Err(HandlerError::Reject(msg))); } @@ -527,13 +540,6 @@ impl DapSession { Ok(()) } - fn require_initialized(&self, request: &Request) -> Result<(), &'static str> { - if self.state == DapState::Fresh && !matches!(&request.command, Command::Initialize(_)) { - return Err("initialize must be sent first"); - } - Ok(()) - } - fn require_stopped(&self) -> Result<(), &'static str> { if self.state != DapState::Stopped { return Err("request requires a stopped frame"); @@ -541,16 +547,8 @@ impl DapSession { Ok(()) } - fn require_thread_id(request: &Request) -> Result<(), &'static str> { - let valid = match &request.command { - Command::StackTrace(args) => args.thread_id == THREAD_ID, - Command::Next(args) => args.thread_id == THREAD_ID, - Command::StepIn(args) => args.thread_id == THREAD_ID, - Command::Continue(args) => args.thread_id == THREAD_ID, - _ => true, - }; - - if !valid { + fn require_thread_id(thread_id: i64) -> Result<(), &'static str> { + if thread_id != THREAD_ID { return Err("unknown threadId"); } Ok(()) From ed18aeaa68718aaa04ac03e542e2bbe70b1c8e75 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Wed, 5 Aug 2026 19:08:39 +0300 Subject: [PATCH 053/100] [Priroda] centralize DAP response sends in run_requests Handlers no longer take Request or call request.success/error themselves; they return HandlerSuccess { response, state, events, outcome } and run_requests is the single send site for both success and error responses, applying state transitions and forwarding events in emitted order. Drop respond_terminated and respond_execution_error -- their response/event construction moves inline at the ExecutionOutcome match arms. send_stopped_event becomes stopped_event_body (pure). dispatch_request takes &Request instead of owning+cloning; handlers receive already-destructured args. handle_unsupported_request takes &Command. HandlerError is gone; handlers return Result so predicate errors bubble via plain ?. Note: state mutations now happen after the response send, not before. If a transport write fails, state is left untouched rather than half-mutated. Wire output is unchanged for the success path. --- src/tools/miri/priroda/src/frontend/dap.rs | 507 ++++++++++----------- 1 file changed, 253 insertions(+), 254 deletions(-) diff --git a/src/tools/miri/priroda/src/frontend/dap.rs b/src/tools/miri/priroda/src/frontend/dap.rs index a44e462a16d66..6e48510cadc5c 100644 --- a/src/tools/miri/priroda/src/frontend/dap.rs +++ b/src/tools/miri/priroda/src/frontend/dap.rs @@ -21,22 +21,23 @@ use crate::debugger::{LocalDesc, PrirodaContext, StepResult}; const THREAD_ID: i64 = 1; const STACK_FRAME_ID: i64 = 1; const LOCALS_VARIABLES_REFERENCE: i64 = 1; -type ServerResult = Result; -enum HandlerOutcome { - Continue, - Exit, +enum HandlerResponse { + Success(ResponseBody), + Error(String), } -enum HandlerError { - Reject(&'static str), - Transport(ServerError), +struct HandlerSuccess { + response: HandlerResponse, + state: Option, + events: Vec, + outcome: HandlerOutcome, } -impl From for HandlerError { - fn from(e: ServerError) -> Self { - HandlerError::Transport(e) - } +#[derive(Clone, Copy, PartialEq, Eq)] +enum HandlerOutcome { + Continue, + Exit, } #[derive(Clone, Copy, PartialEq, Eq)] @@ -63,7 +64,7 @@ impl Dap { &self, session: &mut PrirodaContext<'tcx>, ) -> InterpResult<'tcx> { - if let Err(err) = DapSession::stdio().run_requests(session)? { + if let Err(err) = DapSession::stdio().run_requests(session) { eprintln!("priroda dap error: {err:?}"); } @@ -93,67 +94,61 @@ impl DapSession { fn run_requests<'tcx>( &mut self, session: &mut PrirodaContext<'tcx>, - ) -> InterpResult<'tcx, ServerResult> { + ) -> Result<(), ServerError> { loop { let request = match self.server.poll_request() { Ok(Some(request)) => request, - Ok(None) => return interp_ok(Ok(())), - Err(err) => return interp_ok(Err(err)), + Ok(None) => return Ok(()), + Err(err) => return Err(err), }; - let request_for_dispatch = request.clone(); - - match self.dispatch_request(request_for_dispatch, session)? { - Ok(HandlerOutcome::Continue) => {} - Ok(HandlerOutcome::Exit) => return interp_ok(Ok(())), - Err(HandlerError::Reject(msg)) => { - let response = request.error(msg); - if let Err(err) = self.server.respond(response) { - return interp_ok(Err(err)); + match self.dispatch_request(&request, session) { + Ok(s) => { + let response = match s.response { + HandlerResponse::Success(body) => request.success(body), + HandlerResponse::Error(message) => request.error(&message), + }; + self.server.respond(response)?; + if let Some(st) = s.state { + self.state = st; + } + for ev in s.events { + self.server.send_event(ev)?; + } + if s.outcome == HandlerOutcome::Exit { + return Ok(()); } } - Err(HandlerError::Transport(e)) => return interp_ok(Err(e)), + Err(msg) => { + self.server.respond(request.error(msg))?; + } } } } fn dispatch_request<'tcx>( - &mut self, - request: Request, + &self, + request: &Request, session: &mut PrirodaContext<'tcx>, - ) -> InterpResult<'tcx, Result> { + ) -> Result { if self.state == DapState::Fresh && !matches!(&request.command, Command::Initialize(_)) { - return interp_ok(Err(HandlerError::Reject("initialize must be sent first"))); + return Err("initialize must be sent first"); } - let outcome = match &request.command { - Command::Initialize(_) => self.handle_initialize(request), - Command::Launch(_) => self.handle_launch(request), - Command::ConfigurationDone => return self.handle_configuration_done(request, session), - Command::Threads => self.handle_threads(request), - Command::StackTrace(_) => self.handle_stack_trace(request, session), - Command::Scopes(args) => { - let frame_id = args.frame_id; - self.handle_scopes(request, frame_id, session) - } - Command::Variables(args) => { - let variables_reference = args.variables_reference; - self.handle_variables(request, variables_reference, session) - } - Command::Continue(_) => return self.handle_continue(request, session), - Command::SetBreakpoints(args) => { - let args = args.clone(); - self.handle_set_breakpoints(request, &args, session) - } - Command::Next(_) | Command::StepIn(_) => { - let body = match &request.command { - Command::Next(_) => ResponseBody::Next, - Command::StepIn(_) => ResponseBody::StepIn, - _ => bug!("step body is selected by the outer Next/StepIn match"), - }; - return self.handle_step(request, body, session); - } - Command::Disconnect(_) => self.handle_disconnect(request), + match &request.command { + Command::Initialize(_) => self.handle_initialize(), + Command::Launch(_) => self.handle_launch(), + Command::ConfigurationDone => self.handle_configuration_done(session), + Command::Threads => self.handle_threads(), + Command::StackTrace(args) => self.handle_stack_trace(args.thread_id, session), + Command::Scopes(args) => self.handle_scopes(args.frame_id, session), + Command::Variables(args) => self.handle_variables(args.variables_reference, session), + Command::Continue(args) => self.handle_continue(args.thread_id, session), + Command::SetBreakpoints(args) => self.handle_set_breakpoints(args, session), + Command::Next(args) => self.handle_step(ResponseBody::Next, args.thread_id, session), + Command::StepIn(args) => + self.handle_step(ResponseBody::StepIn, args.thread_id, session), + Command::Disconnect(_) => self.handle_disconnect(), Command::Attach(_) | Command::BreakpointLocations(_) | Command::Cancel(_) @@ -183,29 +178,29 @@ impl DapSession { | Command::StepOut(_) | Command::Terminate(_) | Command::TerminateThreads(_) - | Command::WriteMemory(_) => self.handle_unsupported_request(request), - }; - interp_ok(outcome) + | Command::WriteMemory(_) => self.handle_unsupported_request(&request.command), + } } /// FIXME: connect launch arguments to Priroda's session model. - fn handle_launch(&mut self, request: Request) -> Result { - self.require_state(DapState::Initialized).map_err(HandlerError::Reject)?; + fn handle_launch(&self) -> Result { + self.require_state(DapState::Initialized)?; - let response = request.success(ResponseBody::Launch); - self.server.respond(response)?; - self.state = DapState::Launched; - Ok(HandlerOutcome::Continue) + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::Launch), + state: Some(DapState::Launched), + events: Vec::new(), + outcome: HandlerOutcome::Continue, + }) } fn handle_scopes<'tcx>( - &mut self, - request: Request, + &self, frame_id: i64, session: &PrirodaContext<'tcx>, - ) -> Result { - self.require_stopped().map_err(HandlerError::Reject)?; - Self::require_frame_id(frame_id).map_err(HandlerError::Reject)?; + ) -> Result { + self.require_stopped()?; + Self::require_frame_id(frame_id)?; let (source, line, column) = match &session.current_location { Some(location) => { @@ -230,33 +225,35 @@ impl DapSession { } None => (None, None, None), }; - let response = request.success(ResponseBody::Scopes(ScopesResponse { - scopes: vec![Scope { - name: "Locals".to_string(), - presentation_hint: Some(ScopePresentationhint::Locals), - variables_reference: LOCALS_VARIABLES_REFERENCE, - named_variables: None, - indexed_variables: Some(0), - expensive: false, - source, - line, - column, - end_line: None, - end_column: None, - }], - })); - self.server.respond(response)?; - Ok(HandlerOutcome::Continue) + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::Scopes(ScopesResponse { + scopes: vec![Scope { + name: "Locals".to_string(), + presentation_hint: Some(ScopePresentationhint::Locals), + variables_reference: LOCALS_VARIABLES_REFERENCE, + named_variables: None, + indexed_variables: Some(0), + expensive: false, + source, + line, + column, + end_line: None, + end_column: None, + }], + })), + state: None, + events: Vec::new(), + outcome: HandlerOutcome::Continue, + }) } fn handle_variables<'tcx>( - &mut self, - request: Request, + &self, variables_reference: i64, session: &PrirodaContext<'tcx>, - ) -> Result { - self.require_stopped().map_err(HandlerError::Reject)?; - Self::require_variables_reference(variables_reference).map_err(HandlerError::Reject)?; + ) -> Result { + self.require_stopped()?; + Self::require_variables_reference(variables_reference)?; let variables = if variables_reference == LOCALS_VARIABLES_REFERENCE { session.list_locals().into_iter().map(Self::local_to_variable).collect() @@ -264,65 +261,75 @@ impl DapSession { Vec::new() }; - let response = request.success(ResponseBody::Variables(VariablesResponse { variables })); - self.server.respond(response)?; - Ok(HandlerOutcome::Continue) + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::Variables(VariablesResponse { + variables, + })), + state: None, + events: Vec::new(), + outcome: HandlerOutcome::Continue, + }) } fn handle_configuration_done<'tcx>( - &mut self, - request: Request, + &self, session: &mut PrirodaContext<'tcx>, - ) -> InterpResult<'tcx, Result> { - if let Err(msg) = self.require_state(DapState::Launched) { - return interp_ok(Err(HandlerError::Reject(msg))); - } + ) -> Result { + self.require_state(DapState::Launched)?; match Self::execution_outcome(session.stop_at_first_user_location()) { - ExecutionOutcome::Stopped(_) => { - let response = request.success(ResponseBody::ConfigurationDone); - interp_ok( - self.server - .respond(response) - .and_then(|()| { - self.state = DapState::Stopped; - self.send_stopped_event(StoppedEventReason::Entry) - }) - .map(|()| HandlerOutcome::Continue) - .map_err(HandlerError::Transport), - ) - } + ExecutionOutcome::Stopped(_) => + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::ConfigurationDone), + state: Some(DapState::Stopped), + events: vec![Event::Stopped(Self::stopped_event_body( + StoppedEventReason::Entry, + ))], + outcome: HandlerOutcome::Continue, + }), ExecutionOutcome::Terminated { code } => - interp_ok(self.respond_terminated(request, ResponseBody::ConfigurationDone, code)), + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::ConfigurationDone), + state: Some(DapState::Terminated), + events: vec![ + Event::Exited(ExitedEventBody { exit_code: code.into() }), + Event::Terminated(None), + ], + outcome: HandlerOutcome::Exit, + }), ExecutionOutcome::Failed(message) => - interp_ok(self.respond_execution_error(request, message)), + Ok(HandlerSuccess { + response: HandlerResponse::Error(message), + state: Some(DapState::Terminated), + events: vec![Event::Terminated(None)], + outcome: HandlerOutcome::Exit, + }), } } /// FIXME: replace this with Miri thread state once Priroda exposes a /// frontend-facing thread model. - fn handle_threads(&mut self, request: Request) -> Result { - self.reject_after_termination().map_err(HandlerError::Reject)?; - - let response = request.success(ResponseBody::Threads(ThreadsResponse { - threads: vec![Thread { id: THREAD_ID, name: "main".to_string() }], - })); - self.server.respond(response)?; - Ok(HandlerOutcome::Continue) + fn handle_threads(&self) -> Result { + self.reject_after_termination()?; + + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::Threads(ThreadsResponse { + threads: vec![Thread { id: THREAD_ID, name: "main".to_string() }], + })), + state: None, + events: Vec::new(), + outcome: HandlerOutcome::Continue, + }) } /// FIXME: report all frames once Priroda exposes a frontend-facing stack model. fn handle_stack_trace<'tcx>( - &mut self, - request: Request, + &self, + thread_id: i64, session: &PrirodaContext<'tcx>, - ) -> Result { - let thread_id = match &request.command { - Command::StackTrace(args) => args.thread_id, - _ => bug!("wrong command"), - }; - self.require_stopped().map_err(HandlerError::Reject)?; - Self::require_thread_id(thread_id).map_err(HandlerError::Reject)?; + ) -> Result { + self.require_stopped()?; + Self::require_thread_id(thread_id)?; let stack_frames = match &session.current_location { Some(location) => { @@ -361,118 +368,126 @@ impl DapSession { }; let total_frames: i64 = stack_frames.len().try_into().unwrap_or_else(|_| bug!("frame count exceeds i64")); - let response = request.success(ResponseBody::StackTrace(StackTraceResponse { - stack_frames, - total_frames: Some(total_frames), - })); - self.server.respond(response)?; - Ok(HandlerOutcome::Continue) + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::StackTrace(StackTraceResponse { + stack_frames, + total_frames: Some(total_frames), + })), + state: None, + events: Vec::new(), + outcome: HandlerOutcome::Continue, + }) } /// FIXME: grow capabilities as Priroda adds DAP features. - fn handle_initialize(&mut self, request: Request) -> Result { + fn handle_initialize(&self) -> Result { if self.state != DapState::Fresh { - return Err(HandlerError::Reject("initialize may only be sent once")); + return Err("initialize may only be sent once"); } - let response = request.success(ResponseBody::Initialize(Capabilities { - supports_configuration_done_request: Some(true), - supports_single_thread_execution_requests: Some(true), - ..Capabilities::default() - })); - self.server.respond(response)?; - self.server.send_event(Event::Initialized)?; - self.state = DapState::Initialized; - Ok(HandlerOutcome::Continue) + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::Initialize(Capabilities { + supports_configuration_done_request: Some(true), + supports_single_thread_execution_requests: Some(true), + ..Capabilities::default() + })), + state: Some(DapState::Initialized), + events: vec![Event::Initialized], + outcome: HandlerOutcome::Continue, + }) } /// FIXME: distinguish step-over from step-in once Priroda has call-aware stepping. fn handle_step<'tcx>( - &mut self, - request: Request, + &self, body: ResponseBody, + thread_id: i64, session: &mut PrirodaContext<'tcx>, - ) -> InterpResult<'tcx, Result> { - if let Err(msg) = self.require_stopped() { - return interp_ok(Err(HandlerError::Reject(msg))); - } - let thread_id = match &request.command { - Command::Next(args) => args.thread_id, - Command::StepIn(args) => args.thread_id, - _ => bug!("wrong command"), - }; - if let Err(msg) = Self::require_thread_id(thread_id) { - return interp_ok(Err(HandlerError::Reject(msg))); - } + ) -> Result { + self.require_stopped()?; + Self::require_thread_id(thread_id)?; match Self::execution_outcome(session.step()) { ExecutionOutcome::Stopped(result) => - interp_ok( - self.server - .respond(request.success(body)) - .and_then(|()| { - self.state = DapState::Stopped; - self.send_stopped_event(Self::stopped_reason(result)) - }) - .map(|()| HandlerOutcome::Continue) - .map_err(HandlerError::Transport), - ), + Ok(HandlerSuccess { + response: HandlerResponse::Success(body), + state: Some(DapState::Stopped), + events: vec![Event::Stopped(Self::stopped_event_body(Self::stopped_reason( + result, + )))], + outcome: HandlerOutcome::Continue, + }), ExecutionOutcome::Terminated { code } => - interp_ok(self.respond_terminated(request, body, code)), + Ok(HandlerSuccess { + response: HandlerResponse::Success(body), + state: Some(DapState::Terminated), + events: vec![ + Event::Exited(ExitedEventBody { exit_code: code.into() }), + Event::Terminated(None), + ], + outcome: HandlerOutcome::Exit, + }), ExecutionOutcome::Failed(message) => - interp_ok(self.respond_execution_error(request, message)), + Ok(HandlerSuccess { + response: HandlerResponse::Error(message), + state: Some(DapState::Terminated), + events: vec![Event::Terminated(None)], + outcome: HandlerOutcome::Exit, + }), } } fn handle_continue<'tcx>( - &mut self, - request: Request, + &self, + thread_id: i64, session: &mut PrirodaContext<'tcx>, - ) -> InterpResult<'tcx, Result> { - if let Err(msg) = self.require_stopped() { - return interp_ok(Err(HandlerError::Reject(msg))); - } - let thread_id = match &request.command { - Command::Continue(args) => args.thread_id, - _ => bug!("wrong command"), - }; - if let Err(msg) = Self::require_thread_id(thread_id) { - return interp_ok(Err(HandlerError::Reject(msg))); - } + ) -> Result { + self.require_stopped()?; + Self::require_thread_id(thread_id)?; let body = ResponseBody::Continue(ContinueResponse { all_threads_continued: Some(true) }); match Self::execution_outcome(session.continue_execution()) { ExecutionOutcome::Stopped(result) => - interp_ok( - self.server - .respond(request.success(body)) - .and_then(|()| { - self.state = DapState::Stopped; - self.send_stopped_event(Self::stopped_reason(result)) - }) - .map(|()| HandlerOutcome::Continue) - .map_err(HandlerError::Transport), - ), + Ok(HandlerSuccess { + response: HandlerResponse::Success(body), + state: Some(DapState::Stopped), + events: vec![Event::Stopped(Self::stopped_event_body(Self::stopped_reason( + result, + )))], + outcome: HandlerOutcome::Continue, + }), ExecutionOutcome::Terminated { code } => - interp_ok(self.respond_terminated(request, body, code)), + Ok(HandlerSuccess { + response: HandlerResponse::Success(body), + state: Some(DapState::Terminated), + events: vec![ + Event::Exited(ExitedEventBody { exit_code: code.into() }), + Event::Terminated(None), + ], + outcome: HandlerOutcome::Exit, + }), ExecutionOutcome::Failed(message) => - interp_ok(self.respond_execution_error(request, message)), + Ok(HandlerSuccess { + response: HandlerResponse::Error(message), + state: Some(DapState::Terminated), + events: vec![Event::Terminated(None)], + outcome: HandlerOutcome::Exit, + }), } } fn handle_set_breakpoints<'tcx>( - &mut self, - request: Request, + &self, args: &SetBreakpointsArguments, session: &mut PrirodaContext<'tcx>, - ) -> Result { - self.reject_after_termination().map_err(HandlerError::Reject)?; + ) -> Result { + self.reject_after_termination()?; let Some(ref path_str) = args.source.path else { - return Err(HandlerError::Reject( + return Err( "setBreakpoints requires a source.path; sourceReference loads are not supported", - )); + ); }; let path = std::path::PathBuf::from(path_str); @@ -496,30 +511,38 @@ impl DapSession { } } - let response = - request.success(ResponseBody::SetBreakpoints(SetBreakpointsResponse { breakpoints })); - self.server.respond(response)?; - Ok(HandlerOutcome::Continue) + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::SetBreakpoints( + SetBreakpointsResponse { breakpoints }, + )), + state: None, + events: Vec::new(), + outcome: HandlerOutcome::Continue, + }) } - fn handle_disconnect(&mut self, request: Request) -> Result { - self.server.respond(request.success(ResponseBody::Disconnect))?; - self.state = DapState::Terminated; - self.server.send_event(Event::Terminated(None))?; - Ok(HandlerOutcome::Exit) + fn handle_disconnect(&self) -> Result { + Ok(HandlerSuccess { + response: HandlerResponse::Success(ResponseBody::Disconnect), + state: Some(DapState::Terminated), + events: vec![Event::Terminated(None)], + outcome: HandlerOutcome::Exit, + }) } fn handle_unsupported_request( - &mut self, - request: Request, - ) -> Result { - let message = format!( - "unsupported request in Priroda DAP demo mode: {}", - Self::display_command(&request.command) - ); - let response = request.error(&message); - self.server.respond(response)?; - Ok(HandlerOutcome::Continue) + &self, + command: &Command, + ) -> Result { + Ok(HandlerSuccess { + response: HandlerResponse::Error(format!( + "unsupported request in Priroda DAP demo mode: {}", + Self::display_command(command) + )), + state: None, + events: Vec::new(), + outcome: HandlerOutcome::Continue, + }) } fn reject_after_termination(&self) -> Result<(), &'static str> { @@ -568,30 +591,6 @@ impl DapSession { Ok(()) } - fn respond_execution_error( - &mut self, - request: Request, - message: String, - ) -> Result { - self.state = DapState::Terminated; - self.server.respond(request.error(&message))?; - self.server.send_event(Event::Terminated(None))?; - Ok(HandlerOutcome::Exit) - } - - fn respond_terminated( - &mut self, - request: Request, - body: ResponseBody, - code: i32, - ) -> Result { - self.state = DapState::Terminated; - self.server.respond(request.success(body))?; - self.server.send_event(Event::Exited(ExitedEventBody { exit_code: code.into() }))?; - self.server.send_event(Event::Terminated(None))?; - Ok(HandlerOutcome::Exit) - } - fn execution_outcome<'tcx>(result: InterpResult<'tcx, StepResult>) -> ExecutionOutcome { match result.report_err() { Ok(step) => ExecutionOutcome::Stopped(step), @@ -610,8 +609,8 @@ impl DapSession { ExecutionOutcome::Failed(kind.to_string()) } - fn send_stopped_event(&mut self, reason: StoppedEventReason) -> ServerResult { - self.server.send_event(Event::Stopped(StoppedEventBody { + fn stopped_event_body(reason: StoppedEventReason) -> StoppedEventBody { + StoppedEventBody { reason, description: None, thread_id: Some(THREAD_ID), @@ -619,7 +618,7 @@ impl DapSession { text: None, all_threads_stopped: Some(true), hit_breakpoint_ids: None, - })) + } } fn stopped_reason(result: StepResult) -> StoppedEventReason { From cd8a97b4b21a9c2f8fa1ec9eed2cc6c83c489ddb Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:38:32 +0000 Subject: [PATCH 054/100] Handle LLVM 21 in memchr result codegen test LLVM 21 preserves the bounds assumption but does not eliminate the aggregate phi that LLVM 22 removes. Check each version's supported optimization and restore the shared postcondition so direct callers can eliminate bounds checks. --- library/core/src/slice/memchr.rs | 14 +++----------- .../lib-optimizations/memchr-result.rs | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs index 68826ecac31f3..fb99e86139d7e 100644 --- a/library/core/src/slice/memchr.rs +++ b/library/core/src/slice/memchr.rs @@ -24,18 +24,10 @@ const fn contains_zero_byte(x: usize) -> bool { #[must_use] pub const fn memchr(x: u8, text: &[u8]) -> Option { // Fast path for small slices. - if text.len() < 2 * USIZE_BYTES { - let result = memchr_naive(x, text); - if let Some(index) = result { - // SAFETY: `memchr_naive` only returns an index from within `text`. - unsafe { crate::hint::assert_unchecked(index < text.len()) }; - } - return result; - } - - let result = memchr_aligned(x, text); + let result = + if text.len() < 2 * USIZE_BYTES { memchr_naive(x, text) } else { memchr_aligned(x, text) }; if let Some(index) = result { - // SAFETY: `memchr_aligned` only returns an index from within `text`. + // SAFETY: Both implementations only return an index from within `text`. unsafe { crate::hint::assert_unchecked(index < text.len()) }; } result diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs index 77abc33adde83..beeab470c08af 100644 --- a/tests/codegen-llvm/lib-optimizations/memchr-result.rs +++ b/tests/codegen-llvm/lib-optimizations/memchr-result.rs @@ -1,22 +1,34 @@ // Ensure `memchr` communicates that a returned index is in bounds. //@ compile-flags: -Copt-level=3 -Zinline-mir=false //@ only-x86_64 +//@ revisions: llvm-old llvm-new +//@ [llvm-old] max-llvm-major-version: 21 +//@ [llvm-new] min-llvm-version: 22 #![crate_type = "lib"] #![feature(slice_internals)] extern crate core; -use core::slice::memchr::memrchr; +use core::slice::memchr::{memchr, memrchr}; // CHECK-LABEL: @find_char #[no_mangle] pub fn find_char(haystack: &str, needle: char) -> Option { - // CHECK-NOT: phi { i64, i64 } + // llvm-old: call void @llvm.assume + // llvm-new-NOT: phi { i64, i64 } // CHECK: ret { i64, i64 } haystack.find(needle) } +// CHECK-LABEL: @find_byte +#[no_mangle] +pub fn find_byte(haystack: &[u8], needle: u8) -> Option { + // llvm-new-NOT: panic_bounds_check + // CHECK: ret { i1, i8 } + memchr(needle, haystack).map(|index| haystack[index]) +} + // CHECK-LABEL: @rfind_byte #[no_mangle] pub fn rfind_byte(haystack: &[u8], needle: u8) -> Option { From 2d66ef1d5594d09b1e429c4573003d456b0fded7 Mon Sep 17 00:00:00 2001 From: hkalbasi Date: Sun, 26 Jul 2026 19:28:55 +0330 Subject: [PATCH 055/100] Lookup exported statics when encountering an unsupported imported static --- src/tools/miri/src/machine.rs | 63 +++++++++++++- src/tools/miri/src/shims/foreign_items.rs | 63 +++++++++----- src/tools/miri/src/shims/sig.rs | 52 ++++++------ .../miri/tests/fail/extern_static/clashing.rs | 15 ++++ .../tests/fail/extern_static/clashing.stderr | 21 +++++ .../in_const.rs} | 0 .../in_const.stderr} | 2 +- .../tests/fail/extern_static/mut_mismatch1.rs | 13 +++ .../fail/extern_static/mut_mismatch1.stderr | 13 +++ .../tests/fail/extern_static/mut_mismatch2.rs | 17 ++++ .../fail/extern_static/mut_mismatch2.stderr | 13 +++ .../tests/fail/extern_static/mut_mismatch3.rs | 13 +++ .../fail/extern_static/mut_mismatch3.stderr | 13 +++ .../fail/extern_static/shim_clashing1.rs | 15 ++++ .../fail/extern_static/shim_clashing1.stderr | 16 ++++ .../fail/extern_static/shim_clashing2.rs | 13 +++ .../fail/extern_static/shim_clashing2.stderr | 16 ++++ .../fail/extern_static/type_confusion.rs | 14 ++++ .../fail/extern_static/type_confusion.stderr | 13 +++ .../unsupported.rs} | 0 .../unsupported.stderr} | 2 +- .../fail/extern_static/write_immutable.rs | 29 +++++++ .../fail/extern_static/write_immutable.stderr | 13 +++ .../tests/fail/extern_static/wrong_size.rs | 10 +++ .../fail/extern_static/wrong_size.stderr | 13 +++ .../wrong_size_shim.rs} | 0 .../wrong_size_shim.stderr} | 4 +- .../tests/fail/extern_static/wrong_type.rs | 11 +++ .../fail/extern_static/wrong_type.stderr | 13 +++ .../exported_symbol_shim_clashing.stderr | 7 +- src/tools/miri/tests/pass/extern_static.rs | 83 +++++++++++++++++++ 31 files changed, 511 insertions(+), 59 deletions(-) create mode 100644 src/tools/miri/tests/fail/extern_static/clashing.rs create mode 100644 src/tools/miri/tests/fail/extern_static/clashing.stderr rename src/tools/miri/tests/fail/{extern_static_in_const.rs => extern_static/in_const.rs} (100%) rename src/tools/miri/tests/fail/{extern_static_in_const.stderr => extern_static/in_const.stderr} (89%) create mode 100644 src/tools/miri/tests/fail/extern_static/mut_mismatch1.rs create mode 100644 src/tools/miri/tests/fail/extern_static/mut_mismatch1.stderr create mode 100644 src/tools/miri/tests/fail/extern_static/mut_mismatch2.rs create mode 100644 src/tools/miri/tests/fail/extern_static/mut_mismatch2.stderr create mode 100644 src/tools/miri/tests/fail/extern_static/mut_mismatch3.rs create mode 100644 src/tools/miri/tests/fail/extern_static/mut_mismatch3.stderr create mode 100644 src/tools/miri/tests/fail/extern_static/shim_clashing1.rs create mode 100644 src/tools/miri/tests/fail/extern_static/shim_clashing1.stderr create mode 100644 src/tools/miri/tests/fail/extern_static/shim_clashing2.rs create mode 100644 src/tools/miri/tests/fail/extern_static/shim_clashing2.stderr create mode 100644 src/tools/miri/tests/fail/extern_static/type_confusion.rs create mode 100644 src/tools/miri/tests/fail/extern_static/type_confusion.stderr rename src/tools/miri/tests/fail/{extern_static.rs => extern_static/unsupported.rs} (100%) rename src/tools/miri/tests/fail/{extern_static.stderr => extern_static/unsupported.stderr} (90%) create mode 100644 src/tools/miri/tests/fail/extern_static/write_immutable.rs create mode 100644 src/tools/miri/tests/fail/extern_static/write_immutable.stderr create mode 100644 src/tools/miri/tests/fail/extern_static/wrong_size.rs create mode 100644 src/tools/miri/tests/fail/extern_static/wrong_size.stderr rename src/tools/miri/tests/fail/{extern_static_wrong_size.rs => extern_static/wrong_size_shim.rs} (100%) rename src/tools/miri/tests/fail/{extern_static_wrong_size.stderr => extern_static/wrong_size_shim.stderr} (65%) create mode 100644 src/tools/miri/tests/fail/extern_static/wrong_type.rs create mode 100644 src/tools/miri/tests/fail/extern_static/wrong_type.stderr create mode 100644 src/tools/miri/tests/pass/extern_static.rs diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index f476614992041..3ca5208505205 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -12,12 +12,14 @@ use rand::rngs::StdRng; use rand::{RngExt, SeedableRng}; use rustc_abi::{Align, ExternAbi, Size}; use rustc_apfloat::{Float, FloatConvert}; +use rustc_ast::Mutability; use rustc_ast::expand::allocator::{self, SpecialAllocatorMethod}; use rustc_data_structures::either::Either; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; #[allow(unused)] use rustc_data_structures::static_assert_size; use rustc_hir::attrs::{InlineAttr, Linkage}; +use rustc_hir::def::DefKind; use rustc_log::tracing; use rustc_middle::middle::codegen_fn_attrs::TargetFeatureKind; use rustc_middle::mir; @@ -566,7 +568,7 @@ pub struct MiriMachine<'tcx> { /// Cache of `Instance` exported under the given `Symbol` name. /// `None` means no `Instance` exported under the given name is found. - pub(crate) exported_symbols_cache: FxHashMap>>, + pub(crate) exported_symbols_cache: RefCell>>>, /// Equivalent setting as RUST_BACKTRACE on encountering an error. pub(crate) backtrace_style: BacktraceStyle, @@ -776,7 +778,7 @@ impl<'tcx> MiriMachine<'tcx> { static_roots: Vec::new(), profiler, string_cache: Default::default(), - exported_symbols_cache: FxHashMap::default(), + exported_symbols_cache: RefCell::new(FxHashMap::default()), backtrace_style: config.backtrace_style, user_relevant_crates, extern_statics: FxHashMap::default(), @@ -1462,6 +1464,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { Some(_) => ecx.machine.extern_statics_imports.get(&link_name), }; if let Some(&ptr) = ptr { + ecx.check_shim_symbol_clash(link_name)?; // Various parts of the engine rely on `get_alloc_info` for size and alignment // information. That uses the type information of this static. // Make sure it matches the Miri allocation for this. @@ -1503,7 +1506,61 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { .expect("`missing_weak_symbol` should have been initialized"), ) } else { - throw_unsup_format!("extern static `{link_name}` is not supported by Miri") + // Look for a Rust static with this symbol name in the crate graph. + let Some(instance) = ecx.lookup_exported_static(link_name)? else { + throw_unsup_format!("extern static `{link_name}` is not supported by Miri"); + }; + // Evaluate the static to get its allocation. + let place = ecx.eval_global(instance)?; + let static_ptr = place.ptr().into_pointer_or_addr().unwrap(); + // Validate the allocation matches the declared size and alignment. + let alloc_id = static_ptr.provenance.get_alloc_id().unwrap(); + let info = ecx.get_alloc_info(alloc_id); + if extern_decl_layout.size != info.size || extern_decl_layout.align.abi != info.align { + throw_ub_format!( + "extern static `{link_name}` has been declared as `{krate}::{name}` \ + with a size of {decl_size} bytes and alignment of {decl_align} bytes, \ + but the exported static with that name has a size of {shim_size} bytes and \ + alignment of {shim_align} bytes", + name = ecx.tcx.def_path_str(def_id), + krate = ecx.tcx.crate_name(def_id.krate), + decl_size = extern_decl_layout.size.bytes(), + decl_align = extern_decl_layout.align.bytes(), + shim_size = info.size.bytes(), + shim_align = info.align.bytes(), + ) + } + // Check that the mutability of the declared static matches that of the backing. + // If the backing static can be modified (because it is a `static mut`, or because + // it is a `static` whose type has interior mutability) while the declaration here + // is a non-mut `static` with a `Freeze` type, then the compiler's assumption that + // the value never changes may be violated, so this may cause UB. + // This is somehow defensive, as the allocation might be mutable but no mutation + // ever happens, but this is probably the most precise thing we can do. + // Specially, the second case is very defensive and we may be able to lift it. + let DefKind::Static { mutability, .. } = ecx.tcx.def_kind(def_id) else { + unreachable!("`{def_id:?}` is not a static"); + }; + let decl_is_mut = + !(mutability == Mutability::Not && ecx.type_is_freeze(extern_decl_layout.ty)); + let backing_is_mut = ecx.get_alloc_mutability(alloc_id)? == Mutability::Mut; + if !decl_is_mut && backing_is_mut { + throw_ub_format!( + "extern static `{krate}::{name}` is declared as an immutable `static`, \ + but the backing static is mutable", + name = ecx.tcx.def_path_str(def_id), + krate = ecx.tcx.crate_name(def_id.krate), + ) + } + if decl_is_mut && !backing_is_mut { + throw_ub_format!( + "extern static `{krate}::{name}` is declared as an mutable `static`, \ + but the backing static is immutable", + name = ecx.tcx.def_path_str(def_id), + krate = ecx.tcx.crate_name(def_id.krate), + ) + } + interp_ok(static_ptr) } } diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index a904116017876..80f5369a9bbc1 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -57,7 +57,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { match *shim { Either::Left(other_fn) => { let handler = this - .lookup_exported_symbol(other_fn)? + .lookup_exported_fn(other_fn)? .expect("missing alloc error handler symbol"); return interp_ok(Some(handler)); } @@ -75,7 +75,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // The rest either implements the logic, or falls back to `lookup_exported_symbol`. let res = this.emulate_foreign_item_inner(link_name, abi, args, &dest)?; res.jump_to_next_block(this, &dest.clone().into(), ret, Some(unwind), |this| { - if let Some(body) = this.lookup_exported_symbol(link_name)? { + if let Some(body) = this.lookup_exported_fn(link_name)? { return interp_ok(Some(body)); } @@ -110,17 +110,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(()) } - /// Lookup the body of a function that has `link_name` as the symbol name. + /// Lookup the instance that has `link_name` as the symbol name. fn lookup_exported_symbol( - &mut self, + &self, link_name: Symbol, - ) -> InterpResult<'tcx, Option<(&'tcx mir::Body<'tcx>, ty::Instance<'tcx>)>> { - let this = self.eval_context_mut(); + ) -> InterpResult<'tcx, Option>> { + let this = self.eval_context_ref(); let tcx = this.tcx.tcx; // If the result was cached, just return it. // (Cannot use `or_insert` since the code below might have to throw an error.) - let entry = this.machine.exported_symbols_cache.entry(link_name); + let mut cache = this.machine.exported_symbols_cache.borrow_mut(); + let entry = cache.entry(link_name); let instance = *match entry { Entry::Occupied(e) => e.into_mut(), Entry::Vacant(e) => { @@ -206,25 +207,49 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(()) })?; - // Once we identified the instance corresponding to the symbol, ensure - // it is a function. It is okay to encounter non-functions in the search above - // as long as the final instance we arrive at is a function. - if let Some(SymbolTarget { instance, .. }) = symbol_target { - if !matches!(tcx.def_kind(instance.def_id()), DefKind::Fn | DefKind::AssocFn) { - throw_ub_format!( - "attempt to call an exported symbol that is not defined as a function" - ); - } - } - e.insert(symbol_target.map(|SymbolTarget { instance, .. }| instance)) } }; + drop(cache); + interp_ok(instance) + } + + /// Lookup the body of a function that has `link_name` as the symbol name. + fn lookup_exported_fn( + &self, + link_name: Symbol, + ) -> InterpResult<'tcx, Option<(&'tcx mir::Body<'tcx>, ty::Instance<'tcx>)>> { + let this = self.eval_context_ref(); + let instance = this.lookup_exported_symbol(link_name)?; + if let Some(instance) = &instance { + if !matches!(this.tcx.def_kind(instance.def_id()), DefKind::Fn | DefKind::AssocFn) { + throw_ub_format!( + "attempt to call an exported symbol that is not defined as a function" + ); + } + } match instance { - None => interp_ok(None), // no symbol with this name + None => interp_ok(None), Some(instance) => interp_ok(Some((this.load_mir(instance.def, None)?, instance))), } } + + /// Lookup the instance of a static that has `link_name` as the symbol name. + fn lookup_exported_static( + &self, + link_name: Symbol, + ) -> InterpResult<'tcx, Option>> { + let this = self.eval_context_ref(); + let instance = this.lookup_exported_symbol(link_name)?; + if let Some(instance) = &instance { + if !matches!(this.tcx.def_kind(instance.def_id()), DefKind::Static { .. }) { + throw_ub_format!( + "attempt to access an exported symbol `{link_name}` that is not defined as a static" + ); + } + } + interp_ok(instance) + } } impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {} diff --git a/src/tools/miri/src/shims/sig.rs b/src/tools/miri/src/shims/sig.rs index b0b4bca3f517c..d40e9039f2b60 100644 --- a/src/tools/miri/src/shims/sig.rs +++ b/src/tools/miri/src/shims/sig.rs @@ -200,30 +200,29 @@ fn check_shim_abi<'tcx>( interp_ok(()) } -fn check_shim_symbol_clash<'tcx>( - this: &mut MiriInterpCx<'tcx>, - link_name: Symbol, -) -> InterpResult<'tcx, ()> { - if let Some((body, instance)) = this.lookup_exported_symbol(link_name)? { - // If compiler-builtins is providing the symbol, then don't treat it as a clash. - // We'll use our built-in implementation in `emulate_foreign_item_inner` for increased - // performance. Note that this means we won't catch any undefined behavior in - // compiler-builtins when running other crates, but Miri can still be run on - // compiler-builtins itself (or any crate that uses it as a normal dependency) - if this.tcx.is_compiler_builtins(instance.def_id().krate) { - return interp_ok(()); - } +impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} +pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + /// Ensure the given symbol is not exported by the program. + fn check_shim_symbol_clash(&self, link_name: Symbol) -> InterpResult<'tcx, ()> { + let this = self.eval_context_ref(); + if let Some(instance) = this.lookup_exported_symbol(link_name)? { + // If compiler-builtins is providing the symbol, then don't treat it as a clash. + // We'll use our built-in implementation in `emulate_foreign_item_inner` for increased + // performance. Note that this means we won't catch any undefined behavior in + // compiler-builtins when running other crates, but Miri can still be run on + // compiler-builtins itself (or any crate that uses it as a normal dependency) + if this.tcx.is_compiler_builtins(instance.def_id().krate) { + return interp_ok(()); + } - throw_machine_stop!(TerminationInfo::SymbolShimClashing { - link_name, - span: body.span.data(), - }) + throw_machine_stop!(TerminationInfo::SymbolShimClashing { + link_name, + span: this.tcx.def_span(instance.def_id()).data(), + }) + } + interp_ok(()) } - interp_ok(()) -} -impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} -pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { fn check_shim_sig_lenient<'a, const N: usize>( &mut self, abi: &FnAbi<'tcx, Ty<'tcx>>, @@ -231,8 +230,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { link_name: Symbol, args: &'a [OpTy<'tcx>], ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> { - let this = self.eval_context_mut(); - check_shim_symbol_clash(this, link_name)?; + self.check_shim_symbol_clash(link_name)?; if abi.conv != exp_abi { throw_ub_format!( @@ -283,7 +281,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // Check everything. check_shim_abi(this, callee_fn_abi, caller_fn_abi)?; - check_shim_symbol_clash(this, link_name)?; + this.check_shim_symbol_clash(link_name)?; // Return arguments. if let Ok(ops) = caller_args.try_into() { @@ -304,8 +302,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { where &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>, { - let this = self.eval_context_mut(); - check_shim_symbol_clash(this, link_name)?; + self.check_shim_symbol_clash(link_name)?; if abi.conv != exp_abi { throw_ub_format!( @@ -342,8 +339,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> { assert!(link_name.as_str().starts_with("llvm.")); - let this = self.eval_context_mut(); - check_shim_symbol_clash(this, link_name)?; + self.check_shim_symbol_clash(link_name)?; if let Ok(ops) = args.try_into() { return interp_ok(ops); diff --git a/src/tools/miri/tests/fail/extern_static/clashing.rs b/src/tools/miri/tests/fail/extern_static/clashing.rs new file mode 100644 index 0000000000000..266f4deb1aa83 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/clashing.rs @@ -0,0 +1,15 @@ +#[no_mangle] +static FOO: u8 = 1; +//~^ HELP: it's first defined here, in crate `clashing` + +#[export_name = "FOO"] +static BAR: u8 = 2; +//~^ HELP: then it's defined here again, in crate `clashing` + +fn main() { + extern "Rust" { + static FOO: u8; + } + let _val = &raw const FOO; + //~^ ERROR: multiple definitions of symbol `FOO` +} diff --git a/src/tools/miri/tests/fail/extern_static/clashing.stderr b/src/tools/miri/tests/fail/extern_static/clashing.stderr new file mode 100644 index 0000000000000..0c0c362639ac2 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/clashing.stderr @@ -0,0 +1,21 @@ +error: multiple definitions of symbol `FOO` + --> tests/fail/extern_static/clashing.rs:LL:CC + | +LL | let _val = &raw const FOO; + | ^^^ error occurred here + | +help: it's first defined here, in crate `clashing` + --> tests/fail/extern_static/clashing.rs:LL:CC + | +LL | static FOO: u8 = 1; + | ^^^^^^^^^^^^^^ +help: then it's defined here again, in crate `clashing` + --> tests/fail/extern_static/clashing.rs:LL:CC + | +LL | static BAR: u8 = 2; + | ^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static_in_const.rs b/src/tools/miri/tests/fail/extern_static/in_const.rs similarity index 100% rename from src/tools/miri/tests/fail/extern_static_in_const.rs rename to src/tools/miri/tests/fail/extern_static/in_const.rs diff --git a/src/tools/miri/tests/fail/extern_static_in_const.stderr b/src/tools/miri/tests/fail/extern_static/in_const.stderr similarity index 89% rename from src/tools/miri/tests/fail/extern_static_in_const.stderr rename to src/tools/miri/tests/fail/extern_static/in_const.stderr index f0f0966ea8afe..7cbc11bc6e4cf 100644 --- a/src/tools/miri/tests/fail/extern_static_in_const.stderr +++ b/src/tools/miri/tests/fail/extern_static/in_const.stderr @@ -1,5 +1,5 @@ error: unsupported operation: extern static `E` is not supported by Miri - --> tests/fail/extern_static_in_const.rs:LL:CC + --> tests/fail/extern_static/in_const.rs:LL:CC | LL | let _val = X; | ^ unsupported operation occurred here diff --git a/src/tools/miri/tests/fail/extern_static/mut_mismatch1.rs b/src/tools/miri/tests/fail/extern_static/mut_mismatch1.rs new file mode 100644 index 0000000000000..02340b0c94dc5 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/mut_mismatch1.rs @@ -0,0 +1,13 @@ +//! We want to reserve rights to be able to optimize statics declared as immutable, +//! so we defensively disallow immutable statics pointing to mutable allocations. + +#[export_name = "S"] +static mut BACKING_S: i32 = 42; + +fn main() { + extern "C" { + static S: i32; + } + let _val = &raw const S; + //~^ ERROR: is declared as an immutable `static`, but the backing static is mutable +} diff --git a/src/tools/miri/tests/fail/extern_static/mut_mismatch1.stderr b/src/tools/miri/tests/fail/extern_static/mut_mismatch1.stderr new file mode 100644 index 0000000000000..11c16810edb9d --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/mut_mismatch1.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: extern static `mut_mismatch1::main::S` is declared as an immutable `static`, but the backing static is mutable + --> tests/fail/extern_static/mut_mismatch1.rs:LL:CC + | +LL | let _val = &raw const S; + | ^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static/mut_mismatch2.rs b/src/tools/miri/tests/fail/extern_static/mut_mismatch2.rs new file mode 100644 index 0000000000000..d2c44850f983e --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/mut_mismatch2.rs @@ -0,0 +1,17 @@ +//! We want to reserve rights to be able to optimize statics declared as immutable, +//! so we defensively disallow immutable statics pointing to mutable allocations. + +#![feature(sync_unsafe_cell)] + +use std::cell::SyncUnsafeCell; + +#[export_name = "S"] +static INTERIOR_MUT_S: SyncUnsafeCell = SyncUnsafeCell::new(42); + +fn main() { + extern "C" { + static S: i32; + } + let _val = &raw const S; + //~^ ERROR: is declared as an immutable `static`, but the backing static is mutable +} diff --git a/src/tools/miri/tests/fail/extern_static/mut_mismatch2.stderr b/src/tools/miri/tests/fail/extern_static/mut_mismatch2.stderr new file mode 100644 index 0000000000000..8d7d886c10032 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/mut_mismatch2.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: extern static `mut_mismatch2::main::S` is declared as an immutable `static`, but the backing static is mutable + --> tests/fail/extern_static/mut_mismatch2.rs:LL:CC + | +LL | let _val = &raw const S; + | ^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static/mut_mismatch3.rs b/src/tools/miri/tests/fail/extern_static/mut_mismatch3.rs new file mode 100644 index 0000000000000..c33066341ff13 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/mut_mismatch3.rs @@ -0,0 +1,13 @@ +//! We want to reserve rights to be able to inject implicit writes to mutable declared statics, +//! so we defensively disallow mutable statics pointing to immutable allocations. + +#[export_name = "S"] +static IMMUT_S: i32 = 42; + +fn main() { + extern "C" { + static mut S: i32; + } + let _val = &raw const S; + //~^ ERROR: is declared as an mutable `static`, but the backing static is immutable +} diff --git a/src/tools/miri/tests/fail/extern_static/mut_mismatch3.stderr b/src/tools/miri/tests/fail/extern_static/mut_mismatch3.stderr new file mode 100644 index 0000000000000..dcaff7c4bdf51 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/mut_mismatch3.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: extern static `mut_mismatch3::main::S` is declared as an mutable `static`, but the backing static is immutable + --> tests/fail/extern_static/mut_mismatch3.rs:LL:CC + | +LL | let _val = &raw const S; + | ^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static/shim_clashing1.rs b/src/tools/miri/tests/fail/extern_static/shim_clashing1.rs new file mode 100644 index 0000000000000..36bd4e87104f7 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/shim_clashing1.rs @@ -0,0 +1,15 @@ +//@only-target: linux # we need a specific extern supported on this target + +extern "C" { + static mut environ: *const *const u8; +} + +#[export_name = "environ"] +static mut MY_ENVIRON: *const *const u8 = std::ptr::null(); +//~^ HELP: the `environ` symbol is defined here + +fn main() { + let _val = &raw const MY_ENVIRON; + let _val = &raw const environ; + //~^ ERROR: found `environ` symbol definition that clashes with a built-in shim +} diff --git a/src/tools/miri/tests/fail/extern_static/shim_clashing1.stderr b/src/tools/miri/tests/fail/extern_static/shim_clashing1.stderr new file mode 100644 index 0000000000000..59da90d986b39 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/shim_clashing1.stderr @@ -0,0 +1,16 @@ +error: found `environ` symbol definition that clashes with a built-in shim + --> tests/fail/extern_static/shim_clashing1.rs:LL:CC + | +LL | let _val = &raw const environ; + | ^^^^^^^ error occurred here + | +help: the `environ` symbol is defined here + --> tests/fail/extern_static/shim_clashing1.rs:LL:CC + | +LL | static mut MY_ENVIRON: *const *const u8 = std::ptr::null(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static/shim_clashing2.rs b/src/tools/miri/tests/fail/extern_static/shim_clashing2.rs new file mode 100644 index 0000000000000..ae3f1380bb386 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/shim_clashing2.rs @@ -0,0 +1,13 @@ +//@only-target: linux # we need a specific extern supported on this target + +#[export_name = "environ"] +fn my_environ() {} +//~^ HELP: the `environ` symbol is defined here + +fn main() { + extern "C" { + static environ: *const *const u8; + } + let _val = &raw const environ; + //~^ ERROR: found `environ` symbol definition that clashes with a built-in shim +} diff --git a/src/tools/miri/tests/fail/extern_static/shim_clashing2.stderr b/src/tools/miri/tests/fail/extern_static/shim_clashing2.stderr new file mode 100644 index 0000000000000..fd5f02a06e85f --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/shim_clashing2.stderr @@ -0,0 +1,16 @@ +error: found `environ` symbol definition that clashes with a built-in shim + --> tests/fail/extern_static/shim_clashing2.rs:LL:CC + | +LL | let _val = &raw const environ; + | ^^^^^^^ error occurred here + | +help: the `environ` symbol is defined here + --> tests/fail/extern_static/shim_clashing2.rs:LL:CC + | +LL | fn my_environ() {} + | ^^^^^^^^^^^^^^^ + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static/type_confusion.rs b/src/tools/miri/tests/fail/extern_static/type_confusion.rs new file mode 100644 index 0000000000000..6881cb8c178a7 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/type_confusion.rs @@ -0,0 +1,14 @@ +#[no_mangle] +static FOO: u8 = 42; + +fn main() { + extern "Rust" { + static FOO: bool; + } + // Type confusion between u8 (value 42) and bool: reading as bool is UB + // because 42 is not a valid boolean value (must be 0 or 1). + unsafe { + (&raw const FOO).read(); + //~^ ERROR: /constructing invalid value of type bool/ + } +} diff --git a/src/tools/miri/tests/fail/extern_static/type_confusion.stderr b/src/tools/miri/tests/fail/extern_static/type_confusion.stderr new file mode 100644 index 0000000000000..772f32c3b952b --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/type_confusion.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: constructing invalid value of type bool: encountered 0x2a, but expected a boolean + --> tests/fail/extern_static/type_confusion.rs:LL:CC + | +LL | (&raw const FOO).read(); + | ^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static.rs b/src/tools/miri/tests/fail/extern_static/unsupported.rs similarity index 100% rename from src/tools/miri/tests/fail/extern_static.rs rename to src/tools/miri/tests/fail/extern_static/unsupported.rs diff --git a/src/tools/miri/tests/fail/extern_static.stderr b/src/tools/miri/tests/fail/extern_static/unsupported.stderr similarity index 90% rename from src/tools/miri/tests/fail/extern_static.stderr rename to src/tools/miri/tests/fail/extern_static/unsupported.stderr index e4c51c0345d4c..02a6fdfa5be48 100644 --- a/src/tools/miri/tests/fail/extern_static.stderr +++ b/src/tools/miri/tests/fail/extern_static/unsupported.stderr @@ -1,5 +1,5 @@ error: unsupported operation: extern static `FOO` is not supported by Miri - --> tests/fail/extern_static.rs:LL:CC + --> tests/fail/extern_static/unsupported.rs:LL:CC | LL | let _val = std::ptr::addr_of!(FOO); | ^^^ unsupported operation occurred here diff --git a/src/tools/miri/tests/fail/extern_static/write_immutable.rs b/src/tools/miri/tests/fail/extern_static/write_immutable.rs new file mode 100644 index 0000000000000..d9420ecb86215 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/write_immutable.rs @@ -0,0 +1,29 @@ +//! This test is very similar to `mut_mismatch3`, but actually writes to the static. +//! In case we relaxed `mut_mismatch3` UB, we still want this to remain UB. + +#![feature(sync_unsafe_cell)] + +use std::cell::SyncUnsafeCell; + +#[no_mangle] +static IMMUT: i32 = 42; + +#[no_mangle] +static INTERIOR_MUT: SyncUnsafeCell = SyncUnsafeCell::new(42); + +fn main() { + unsafe { + extern "C" { + static mut INTERIOR_MUT: i32; + } + (&raw mut INTERIOR_MUT).write(7); + } + + unsafe { + extern "C" { + static mut IMMUT: i32; + } + (&raw mut IMMUT).write(7); + //~^ ERROR: is declared as an mutable `static`, but the backing static is immutable + } +} diff --git a/src/tools/miri/tests/fail/extern_static/write_immutable.stderr b/src/tools/miri/tests/fail/extern_static/write_immutable.stderr new file mode 100644 index 0000000000000..74259b36f351d --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/write_immutable.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: extern static `write_immutable::main::IMMUT` is declared as an mutable `static`, but the backing static is immutable + --> tests/fail/extern_static/write_immutable.rs:LL:CC + | +LL | (&raw mut IMMUT).write(7); + | ^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static/wrong_size.rs b/src/tools/miri/tests/fail/extern_static/wrong_size.rs new file mode 100644 index 0000000000000..d8a53a04df88e --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/wrong_size.rs @@ -0,0 +1,10 @@ +#[no_mangle] +static FOO: u8 = 42; + +fn main() { + extern "Rust" { + static FOO: u16; + } + let _val = unsafe { (&raw const FOO).read() }; + //~^ ERROR: extern static `FOO` has been declared as `wrong_size::main::FOO` with a size of 2 bytes +} diff --git a/src/tools/miri/tests/fail/extern_static/wrong_size.stderr b/src/tools/miri/tests/fail/extern_static/wrong_size.stderr new file mode 100644 index 0000000000000..8cea8376dfd2f --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/wrong_size.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: extern static `FOO` has been declared as `wrong_size::main::FOO` with a size of 2 bytes and alignment of 2 bytes, but the exported static with that name has a size of 1 bytes and alignment of 1 bytes + --> tests/fail/extern_static/wrong_size.rs:LL:CC + | +LL | let _val = unsafe { (&raw const FOO).read() }; + | ^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/extern_static_wrong_size.rs b/src/tools/miri/tests/fail/extern_static/wrong_size_shim.rs similarity index 100% rename from src/tools/miri/tests/fail/extern_static_wrong_size.rs rename to src/tools/miri/tests/fail/extern_static/wrong_size_shim.rs diff --git a/src/tools/miri/tests/fail/extern_static_wrong_size.stderr b/src/tools/miri/tests/fail/extern_static/wrong_size_shim.stderr similarity index 65% rename from src/tools/miri/tests/fail/extern_static_wrong_size.stderr rename to src/tools/miri/tests/fail/extern_static/wrong_size_shim.stderr index 0862f97792872..d3a0f0205ee3b 100644 --- a/src/tools/miri/tests/fail/extern_static_wrong_size.stderr +++ b/src/tools/miri/tests/fail/extern_static/wrong_size_shim.stderr @@ -1,5 +1,5 @@ -error: unsupported operation: extern static `environ` has been declared as `extern_static_wrong_size::environ` with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of N bytes and alignment of N bytes - --> tests/fail/extern_static_wrong_size.rs:LL:CC +error: unsupported operation: extern static `environ` has been declared as `wrong_size_shim::environ` with a size of 1 bytes and alignment of 1 bytes, but Miri emulates it via an extern static shim with a size of N bytes and alignment of N bytes + --> tests/fail/extern_static/wrong_size_shim.rs:LL:CC | LL | let _val = unsafe { environ }; | ^^^^^^^ unsupported operation occurred here diff --git a/src/tools/miri/tests/fail/extern_static/wrong_type.rs b/src/tools/miri/tests/fail/extern_static/wrong_type.rs new file mode 100644 index 0000000000000..81683b3ee9da2 --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/wrong_type.rs @@ -0,0 +1,11 @@ +#[allow(non_snake_case)] +#[no_mangle] +fn FOO() {} + +fn main() { + extern "Rust" { + static FOO: (); + } + let _val = &raw const FOO; + //~^ ERROR: attempt to access an exported symbol `FOO` that is not defined as a static +} diff --git a/src/tools/miri/tests/fail/extern_static/wrong_type.stderr b/src/tools/miri/tests/fail/extern_static/wrong_type.stderr new file mode 100644 index 0000000000000..ba38acd3ce16b --- /dev/null +++ b/src/tools/miri/tests/fail/extern_static/wrong_type.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: attempt to access an exported symbol `FOO` that is not defined as a static + --> tests/fail/extern_static/wrong_type.rs:LL:CC + | +LL | let _val = &raw const FOO; + | ^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.stderr b/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.stderr index 0e2b4da5c0a03..5c013b862a7a0 100644 --- a/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.stderr +++ b/src/tools/miri/tests/fail/function_calls/exported_symbol_shim_clashing.stderr @@ -7,11 +7,8 @@ LL | malloc(0); help: the `malloc` symbol is defined here --> tests/fail/function_calls/exported_symbol_shim_clashing.rs:LL:CC | -LL | / extern "C" fn malloc(_: usize) -> *mut std::ffi::c_void { -LL | | -LL | | unreachable!() -LL | | } - | |_^ +LL | extern "C" fn malloc(_: usize) -> *mut std::ffi::c_void { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace diff --git a/src/tools/miri/tests/pass/extern_static.rs b/src/tools/miri/tests/pass/extern_static.rs new file mode 100644 index 0000000000000..70b8ff304c086 --- /dev/null +++ b/src/tools/miri/tests/pass/extern_static.rs @@ -0,0 +1,83 @@ +#![feature(sync_unsafe_cell)] + +use std::cell::SyncUnsafeCell; + +#[no_mangle] +static FOO: u8 = 42; + +#[export_name = "BAR_EXPORTED"] +static BAR_LOCAL_NAME: u16 = 1000; + +#[no_mangle] +static mut MUTABLE_STATIC: i32 = -1; + +#[export_name = "MY_LINK_NAME"] +static RUST_SYMBOL: u32 = 7; + +#[no_mangle] +static FOO_U32: u32 = 42; + +#[no_mangle] +static INTERIOR_MUT: SyncUnsafeCell = SyncUnsafeCell::new(42); + +fn increase_mutable_static_by_original_def(add_val: i32) { + unsafe { + let new_val = (&raw mut MUTABLE_STATIC).read() + add_val; + (&raw mut MUTABLE_STATIC).write(new_val); + } +} + +fn main() { + // The loop ensures we hit both the uncached and cached case. + for _ in 0..3 { + extern "Rust" { + static FOO: u8; + } + + assert_eq!(unsafe { (&raw const FOO).read() }, 42); + + extern "C" { + static BAR_EXPORTED: u16; + } + + assert_eq!(unsafe { (&raw const BAR_EXPORTED).read() }, 1000); + + extern "C" { + #[link_name = "MY_LINK_NAME"] + static EXTERN_STATIC: u32; + } + + assert_eq!(unsafe { (&raw const EXTERN_STATIC).read() }, 7); + + // Ensure that SyncUnsafeCell and `static mut` are interchangable. + extern "C" { + #[link_name = "INTERIOR_MUT"] + static mut INTERIOR_MUT_AS_MUTABLE_STATIC: i32; + #[link_name = "MUTABLE_STATIC"] + static MUTABLE_STATIC_AS_INTERIOR_MUT: SyncUnsafeCell; + } + unsafe { + (&raw mut INTERIOR_MUT_AS_MUTABLE_STATIC).write(7); + MUTABLE_STATIC_AS_INTERIOR_MUT.get().write(3); + } + } + + extern "Rust" { + static mut MUTABLE_STATIC: i32; + } + + // Check what happens if we mix accesses via the two aliases: the original + // definition at the top of the file, and the extern declaration just above. + unsafe { + assert_eq!((&raw const MUTABLE_STATIC).read(), 3); + (&raw mut MUTABLE_STATIC).write(32); + increase_mutable_static_by_original_def(10); + assert_eq!((&raw const MUTABLE_STATIC).read(), 42); + } + + extern "Rust" { + static FOO_U32: i32; + } + // This is like a transmute between raw pointers, so not UB. + assert_eq!(unsafe { (&raw const FOO_U32).read() }, 42i32); +} From a44d862247615198340c4da8fb06f824e75ed2c4 Mon Sep 17 00:00:00 2001 From: The Miri Cronjob Bot Date: Thu, 6 Aug 2026 05:27:12 +0000 Subject: [PATCH 056/100] Prepare for merging from rust-lang/rust This updates the rust-version file to f73951df0a5566d94d13b7954acd9f4ab1fa3734. --- src/tools/miri/rust-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version index f29c624515673..8ab1fcaae5225 100644 --- a/src/tools/miri/rust-version +++ b/src/tools/miri/rust-version @@ -1 +1 @@ -7218ebe93668f51a94a572b690c433dfdbdc2c3d +f73951df0a5566d94d13b7954acd9f4ab1fa3734 From 6ea57afda2eb91a53011b4a8d6ab481c674322ed Mon Sep 17 00:00:00 2001 From: Zalathar Date: Thu, 6 Aug 2026 15:57:31 +1000 Subject: [PATCH 057/100] Snapshot test for `./x fix compiler` --- src/bootstrap/src/core/builder/tests.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index dddb70b3fd468..68f0419e5b731 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -3086,6 +3086,15 @@ mod snapshot { [run] rustc 0 -> miri 1 "); } + + #[test] + fn fix_compiler() { + let ctx = TestCtx::new(); + insta::assert_snapshot!(ctx.config("fix").path("compiler").render_steps(), @r" + [build] llvm + [check] rustc 0 -> rustc 1 (74 crates) + "); + } } struct ExecutedSteps { From 938bf98d284b6db5777568b188f8b0a0f97882ba Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 5 Aug 2026 21:14:26 +1000 Subject: [PATCH 058/100] Inline and remove constructors from `check::Rustc` These extra layers of indirection are more confusing than helpful. --- src/bootstrap/src/core/build_steps/check.rs | 35 +++++++-------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs index 3c4815993b786..a80a34697799d 100644 --- a/src/bootstrap/src/core/build_steps/check.rs +++ b/src/bootstrap/src/core/build_steps/check.rs @@ -225,11 +225,11 @@ impl Step for PrepareRustcRmetaSysroot { fn run(self, builder: &Builder<'_>) -> Self::Output { // Check rustc - let stamp = builder.ensure(Rustc::from_build_compiler( - self.build_compiler.clone(), - self.target, - vec![], - )); + let stamp = builder.ensure(Rustc { + build_compiler: self.build_compiler.clone(), + target: self.target, + crates: vec![], + }); let build_compiler = self.build_compiler.build_compiler(); @@ -285,8 +285,9 @@ impl Step for PrepareStdRmetaSysroot { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Rustc { /// Compiler that will check this rustc. - pub build_compiler: CompilerForCheck, - pub target: TargetSelection, + build_compiler: CompilerForCheck, + target: TargetSelection, + /// Whether to build only a subset of crates. /// /// This shouldn't be used from other steps; see the comment on [`compile::Rustc`]. @@ -295,21 +296,6 @@ pub struct Rustc { crates: Vec, } -impl Rustc { - pub fn new(builder: &Builder<'_>, target: TargetSelection, crates: Vec) -> Self { - let build_compiler = prepare_compiler_for_check(builder, target, Mode::Rustc); - Self::from_build_compiler(build_compiler, target, crates) - } - - fn from_build_compiler( - build_compiler: CompilerForCheck, - target: TargetSelection, - crates: Vec, - ) -> Self { - Self { build_compiler, target, crates } - } -} - impl CommandLineStep for Rustc { type Output = BuildStamp; const IS_HOST: bool = true; @@ -323,8 +309,11 @@ impl CommandLineStep for Rustc { } fn make_run(run: RunConfig<'_>) { + let target = run.target; + let build_compiler = prepare_compiler_for_check(run.builder, target, Mode::Rustc); let crates = run.make_run_crates(Alias::Compiler); - run.builder.ensure(Rustc::new(run.builder, run.target, crates)); + + run.builder.ensure(Rustc { build_compiler, target, crates }); } /// Check the compiler. From b344260f62752cc6715bd77e332e511f900b2899 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Wed, 5 Aug 2026 21:25:23 +1000 Subject: [PATCH 059/100] Store and use an explicit CheckKind in `check::Rustc` This has the pleasant side-effect of making `./x fix compiler` actually work, without breaking `./x clippy` (which relied on the hardcoded `Kind::Check`). --- src/bootstrap/src/core/build_steps/check.rs | 62 ++++++++++++++++----- src/bootstrap/src/core/builder/mod.rs | 2 +- src/bootstrap/src/core/builder/tests.rs | 2 +- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/check.rs b/src/bootstrap/src/core/build_steps/check.rs index a80a34697799d..f3bc9840f8e45 100644 --- a/src/bootstrap/src/core/build_steps/check.rs +++ b/src/bootstrap/src/core/build_steps/check.rs @@ -20,6 +20,23 @@ use crate::core::config::TargetSelection; use crate::utils::build_stamp::{self, BuildStamp}; use crate::{CodegenBackendKind, Compiler, Mode, Subcommand, t}; +/// Allows individual check-step instances to keep track of whether they +/// represent `cargo check` or `cargo fix`, independently of [`Builder::kind`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum CheckKind { + Check, + Fix, +} + +impl CheckKind { + fn to_kind(self) -> Kind { + match self { + CheckKind::Check => Kind::Check, + CheckKind::Fix => Kind::Fix, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Std { /// Compiler that will check this std. @@ -225,11 +242,7 @@ impl Step for PrepareRustcRmetaSysroot { fn run(self, builder: &Builder<'_>) -> Self::Output { // Check rustc - let stamp = builder.ensure(Rustc { - build_compiler: self.build_compiler.clone(), - target: self.target, - crates: vec![], - }); + let stamp = Rustc::check_rustc_for_preparing_sysroot(builder, &self); let build_compiler = self.build_compiler.build_compiler(); @@ -284,6 +297,8 @@ impl Step for PrepareStdRmetaSysroot { /// Checks rustc using `build_compiler`. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Rustc { + check_kind: CheckKind, + /// Compiler that will check this rustc. build_compiler: CompilerForCheck, target: TargetSelection, @@ -296,6 +311,21 @@ pub struct Rustc { crates: Vec, } +impl Rustc { + fn check_rustc_for_preparing_sysroot( + builder: &Builder<'_>, + prepare: &PrepareRustcRmetaSysroot, + ) -> BuildStamp { + builder.ensure(Rustc { + // We specifically want `cargo check`, not the current bootstrap subcommand. + check_kind: CheckKind::Check, + build_compiler: prepare.build_compiler.clone(), + target: prepare.target, + crates: vec![], + }) + } +} + impl CommandLineStep for Rustc { type Output = BuildStamp; const IS_HOST: bool = true; @@ -309,11 +339,17 @@ impl CommandLineStep for Rustc { } fn make_run(run: RunConfig<'_>) { + let check_kind = match run.builder.kind { + Kind::Check => CheckKind::Check, + Kind::Fix => CheckKind::Fix, + kind => panic!("unexpected kind for `check::Rustc`: {kind:?}"), + }; + let target = run.target; let build_compiler = prepare_compiler_for_check(run.builder, target, Mode::Rustc); let crates = run.make_run_crates(Alias::Compiler); - run.builder.ensure(Rustc { build_compiler, target, crates }); + run.builder.ensure(Rustc { check_kind, build_compiler, target, crates }); } /// Check the compiler. @@ -333,7 +369,7 @@ impl CommandLineStep for Rustc { Mode::Rustc, SourceType::InTree, target, - Kind::Check, + self.check_kind.to_kind(), ); rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates); @@ -347,7 +383,7 @@ impl CommandLineStep for Rustc { } let _guard = builder.msg( - Kind::Check, + self.check_kind.to_kind(), format_args!("compiler artifacts{}", crate_description(&self.crates)), Mode::Rustc, self.build_compiler.build_compiler(), @@ -370,13 +406,11 @@ impl CommandLineStep for Rustc { } fn metadata(&self) -> Option { - let metadata = StepMetadata::check("rustc", self.target) + let mut metadata = StepMetadata::new("rustc", self.target, self.check_kind.to_kind()) .built_by(self.build_compiler.build_compiler()); - let metadata = if self.crates.is_empty() { - metadata - } else { - metadata.with_metadata(format!("({} crates)", self.crates.len())) - }; + if !self.crates.is_empty() { + metadata = metadata.with_metadata(format!("({} crates)", self.crates.len())); + } Some(metadata) } } diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index 603ef65854cf6..ccb6efb8bd723 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -229,7 +229,7 @@ impl StepMetadata { Self::new(name, target, Kind::Run) } - fn new(name: &str, target: TargetSelection, kind: Kind) -> Self { + pub fn new(name: &str, target: TargetSelection, kind: Kind) -> Self { Self { name: name.to_string(), kind, target, built_by: None, stage: None, metadata: None } } diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index 68f0419e5b731..57f50d981d1c4 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -3092,7 +3092,7 @@ mod snapshot { let ctx = TestCtx::new(); insta::assert_snapshot!(ctx.config("fix").path("compiler").render_steps(), @r" [build] llvm - [check] rustc 0 -> rustc 1 (74 crates) + [fix] rustc 0 -> rustc 1 (74 crates) "); } } From fd7e845b9e00f14391e098bd6c146024b04993e4 Mon Sep 17 00:00:00 2001 From: Marius Melzer Date: Fri, 9 Jan 2026 18:07:12 +0100 Subject: [PATCH 060/100] Add documentation and maintainer for L4Re target --- src/doc/rustc/src/SUMMARY.md | 1 + src/doc/rustc/src/platform-support.md | 3 +- src/doc/rustc/src/platform-support/l4re.md | 63 ++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/doc/rustc/src/platform-support/l4re.md diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md index ca5890840581c..bedfa65ac894d 100644 --- a/src/doc/rustc/src/SUMMARY.md +++ b/src/doc/rustc/src/SUMMARY.md @@ -84,6 +84,7 @@ - [avr-none](platform-support/avr-none.md) - [\*-espidf](platform-support/esp-idf.md) - [\*-unknown-fuchsia](platform-support/fuchsia.md) + - [\*-unknown-l4re](platform-support/l4re.md) - [\*-unknown-trusty](platform-support/trusty.md) - [\*-kmc-solid_\*](platform-support/kmc-solid.md) - [csky-unknown-linux-gnuabiv2\*](platform-support/csky-unknown-linux-gnuabiv2.md) diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 81e843263487c..c8ae02b091034 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -273,6 +273,7 @@ target | std | host | notes [`aarch64-unknown-helenos`](platform-support/helenos.md) | ✓ | | ARM64 HelenOS [`aarch64-unknown-hermit`](platform-support/hermit.md) | ✓ | | ARM64 Hermit [`aarch64-unknown-illumos`](platform-support/illumos.md) | ✓ | ✓ | ARM64 illumos +[`aarch64-unknown-l4re-uclibc`](platform-support/l4re.md) | ✓ | | ARM64 L4Re with uclibc `aarch64-unknown-linux-gnu_ilp32` | ✓ | ✓ | ARM64 Linux (ILP32 ABI) [`aarch64-unknown-linux-pauthtest`](platform-support/aarch64-unknown-linux-pauthtest.md) | ✓ | ✓ | ARM64 PAC ELF ABI [`aarch64-unknown-managarm-mlibc`](platform-support/managarm.md) | ? | | ARM64 Managarm @@ -459,7 +460,7 @@ target | std | host | notes [`x86_64-unknown-hermit`](platform-support/hermit.md) | ✓ | | x86_64 Hermit [`x86_64-unknown-helenos`](platform-support/helenos.md) | ✓ | | x86_64 (amd64) HelenOS [`x86_64-unknown-hurd-gnu`](platform-support/hurd.md) | ✓ | ✓ | 64-bit GNU/Hurd -`x86_64-unknown-l4re-uclibc` | ? | | +[`x86_64-unknown-l4re-uclibc`](platform-support/l4re.md) | ✓ | | x86_64 L4Re with uclibc [`x86_64-unknown-linux-none`](platform-support/x86_64-unknown-linux-none.md) | * | | 64-bit Linux with no libc [`x86_64-unknown-managarm-mlibc`](platform-support/managarm.md) | ? | | x86_64 Managarm [`x86_64-unknown-motor`](platform-support/motor.md) | ✓ | | x86_64 Motor OS diff --git a/src/doc/rustc/src/platform-support/l4re.md b/src/doc/rustc/src/platform-support/l4re.md new file mode 100644 index 0000000000000..56044319dc77a --- /dev/null +++ b/src/doc/rustc/src/platform-support/l4re.md @@ -0,0 +1,63 @@ +# `*-l4re-uclibc` + +**Tier: 3** + +[L4Re] is an open source, microkernel-based operating system and hypervisor. + +Target triplets available so far: + +- x86_64-unknown-l4re-uclibc +- aarch64-unknown-l4re-uclibc + +## Target maintainers + +- Marius Melzer ([@farao](https://github.com/farao)) + +## Requirements + +The L4Re targets are cross-compiled from a host environment, commonly Linux. +See [Getting Started] for options to set up L4Re. + +The L4Re sources can be found in the [Github Repos]. + +## Building an L4Re Rust Toolchain + +Configure one or several of the above L4Re targets and also add the host triple +in config.toml and build Rust as documented. Start off the toolchain by copying +`build/host/stage2/` to a self-chosen location. + +For each target, build an L4Re sysroot directory by running `make sysroot` in +the L4Re build directory. Copy the content of `sysroot/usr/lib/` into the +`self-contained` directory of the respective target in the Rust Toolchain +directory tree. + +Use rustup to install the L4Re Rust Toolchain locally: + +```sh +rustup toolchain link l4re +``` + +Now use the toolchain via a cargo (or directly a rustc) installed via `rustup`: + +```sh +cargo +l4re build --target +``` + +or + +```sh +rustc +l4re --target +``` + +## Run Rust Programs on L4Re + +You can run an L4Re application written in Rust just like any other externally +built (meaning not build with the L4Re build system) L4Re binary. A good option +is to build an L4Re image and add the application binary to the image and run it +via the ned script. The image can then be put on hardware or run on Qemu. + +See [l4re.org](https://l4re.org) for more information. + +[L4Re]: https://l4re.org +[Getting Started]: https://l4re.org/getting_started +[Github Repos]: https://github.com/L4Re From 5bb8d31122d3f1a13cc338804f2e032be8f04c8d Mon Sep 17 00:00:00 2001 From: Marius Melzer Date: Fri, 9 Jan 2026 17:57:53 +0100 Subject: [PATCH 061/100] Add aarch64 architecture for L4Re target --- compiler/rustc_target/src/spec/mod.rs | 1 + .../targets/aarch64_unknown_l4re_uclibc.rs | 28 +++++++++++++++++++ src/bootstrap/src/core/sanity.rs | 1 + tests/assembly-llvm/targets/targets-elf.rs | 3 ++ 4 files changed, 33 insertions(+) create mode 100644 compiler/rustc_target/src/spec/targets/aarch64_unknown_l4re_uclibc.rs diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 1f17173953643..25465fc29f945 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -1569,6 +1569,7 @@ supported_targets! { ("avr-none", avr_none), + ("aarch64-unknown-l4re-uclibc", aarch64_unknown_l4re_uclibc), ("x86_64-unknown-l4re-uclibc", x86_64_unknown_l4re_uclibc), ("aarch64-unknown-redox", aarch64_unknown_redox), diff --git a/compiler/rustc_target/src/spec/targets/aarch64_unknown_l4re_uclibc.rs b/compiler/rustc_target/src/spec/targets/aarch64_unknown_l4re_uclibc.rs new file mode 100644 index 0000000000000..bca1195ddad72 --- /dev/null +++ b/compiler/rustc_target/src/spec/targets/aarch64_unknown_l4re_uclibc.rs @@ -0,0 +1,28 @@ +use crate::spec::{Arch, Cc, LinkerFlavor, Target, TargetOptions, base}; + +pub(crate) fn target() -> Target { + let mut base = base::l4re::opts(); + + let extra_link_args = &["-zmax-page-size=0x1000", "-zcommon-page-size=0x1000"]; + base.add_pre_link_args(LinkerFlavor::Unix(Cc::Yes), extra_link_args); + base.add_pre_link_args(LinkerFlavor::Unix(Cc::No), extra_link_args); + + Target { + llvm_target: "aarch64-unknown-l4re-uclibc".into(), + metadata: crate::spec::TargetMetadata { + description: Some("Arm64 L4Re".into()), + tier: Some(3), + host_tools: Some(false), + std: Some(true), + }, + pointer_width: 64, + data_layout: "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32".into(), + arch: Arch::AArch64, + options: TargetOptions { + features: "+v8a".into(), + mcount: "__mcount".into(), + max_atomic_width: Some(128), + ..base + } + } +} diff --git a/src/bootstrap/src/core/sanity.rs b/src/bootstrap/src/core/sanity.rs index 400d0715a4738..e4942a8ce669f 100644 --- a/src/bootstrap/src/core/sanity.rs +++ b/src/bootstrap/src/core/sanity.rs @@ -34,6 +34,7 @@ pub struct Finder { /// when the newly-bumped stage 0 compiler now knows about the formerly-missing targets. const STAGE0_MISSING_TARGETS: &[&str] = &[ // just a dummy comment so the list doesn't get onelined + "aarch64-unknown-l4re-uclibc", ]; /// Minimum version threshold for libstdc++ required when using prebuilt LLVM diff --git a/tests/assembly-llvm/targets/targets-elf.rs b/tests/assembly-llvm/targets/targets-elf.rs index 0f9f68cfde787..49bced1dd5bd2 100644 --- a/tests/assembly-llvm/targets/targets-elf.rs +++ b/tests/assembly-llvm/targets/targets-elf.rs @@ -46,6 +46,9 @@ //@ revisions: aarch64_unknown_illumos //@ [aarch64_unknown_illumos] compile-flags: --target aarch64-unknown-illumos //@ [aarch64_unknown_illumos] needs-llvm-components: aarch64 +//@ revisions: aarch64_unknown_l4re_uclibc +//@ [aarch64_unknown_l4re_uclibc] compile-flags: --target aarch64-unknown-l4re-uclibc +//@ [aarch64_unknown_l4re_uclibc] needs-llvm-components: aarch64 //@ revisions: aarch64_unknown_linux_gnu //@ [aarch64_unknown_linux_gnu] compile-flags: --target aarch64-unknown-linux-gnu //@ [aarch64_unknown_linux_gnu] needs-llvm-components: aarch64 From 225ca5fe1127509235266e12f0e3524327be2e26 Mon Sep 17 00:00:00 2001 From: Havard Eidnes Date: Thu, 6 Aug 2026 10:27:11 +0000 Subject: [PATCH 062/100] platform-support/netbsd.md: No longer mention 8.x, due to EoL. Also change the pkgsrc-wip link to indicate a more current rust version. To be re-visited again once 9.x reaches end of maintnance and EoL by the end of the current month. --- src/doc/rustc/src/platform-support/netbsd.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/doc/rustc/src/platform-support/netbsd.md b/src/doc/rustc/src/platform-support/netbsd.md index f7b57fff8a1f9..0d060f1de4cf5 100644 --- a/src/doc/rustc/src/platform-support/netbsd.md +++ b/src/doc/rustc/src/platform-support/netbsd.md @@ -24,10 +24,11 @@ are currently defined running NetBSD: | 3 | `sparc64-unknown-netbsd` | [Sun UltraSPARC systems](https://wiki.netbsd.org/ports/sparc64/) | All use the "native" `stdc++` library which goes along with the natively -supplied GNU C++ compiler for the given OS version. Many of the bootstraps -are built for NetBSD 9.x, although some exceptions exist (some -are built for NetBSD 8.x but also work on newer OS versions). -`x86_64-unknown-netbsd` is built for NetBSD 10.x to access a newer gcc. +supplied GNU C++ compiler for the given OS version. Most of the bootstraps +are built for NetBSD 9.x, although some exceptions exist (some are +built for newer NetBSD versions, due to target becoming usable first +with newer versions). `x86_64-unknown-netbsd` is built for NetBSD +10.x to access a newer gcc. ## Target Maintainers @@ -37,7 +38,7 @@ are built for NetBSD 8.x but also work on newer OS versions). Further contacts: -- [NetBSD/pkgsrc-wip's rust](https://github.com/NetBSD/pkgsrc-wip/blob/master/rust188/Makefile) maintainer (see MAINTAINER variable). This package is part of "pkgsrc work-in-progress" and is used for deployment and testing of new versions of rust. Note that we have the convention of having multiple rust versions active in pkgsrc-wip at any one time, so the version number is part of the directory name, and from time to time old versions are culled so this is not a fully "stable" link. +- [NetBSD/pkgsrc-wip's rust](https://github.com/NetBSD/pkgsrc-wip/blob/master/rust197/Makefile) maintainer (see MAINTAINER variable). This package is part of "pkgsrc work-in-progress" and is used for deployment and testing of new versions of rust. Note that we have the convention of having multiple rust versions active in pkgsrc-wip at any one time, so the version number is part of the directory name, and from time to time old versions are culled so this is not a fully "stable" link. - [NetBSD's pkgsrc lang/rust](https://github.com/NetBSD/pkgsrc/tree/trunk/lang/rust) for the "proper" package in pkgsrc. - [NetBSD's pkgsrc lang/rust-bin](https://github.com/NetBSD/pkgsrc/tree/trunk/lang/rust-bin) which re-uses the bootstrap kit as a binary distribution and therefore avoids the rather protracted native build time of rust itself From 0d75b8c356f894d5b134226c309a3df254eff977 Mon Sep 17 00:00:00 2001 From: im-lunex Date: Thu, 6 Aug 2026 16:21:16 +0600 Subject: [PATCH 063/100] fix ICE in `suggest_add_reference_to_arg` for non-callable items --- .../src/error_reporting/traits/suggestions.rs | 13 +++- tests/ui/structs/ice-missing-field-fn-sig.rs | 14 +++++ .../structs/ice-missing-field-fn-sig.stderr | 61 +++++++++++++++++++ 3 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 tests/ui/structs/ice-missing-field-fn-sig.rs create mode 100644 tests/ui/structs/ice-missing-field-fn-sig.stderr diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 2a6e2a539c964..633b23a1f28ca 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -13,7 +13,7 @@ use rustc_errors::{ Applicability, Diag, EmissionGuarantee, MultiSpan, Style, SuggestionStyle, pluralize, struct_span_code_err, }; -use rustc_hir::def::{CtorOf, DefKind, Res}; +use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::{Visitor, VisitorExt}; use rustc_hir::lang_items::LangItem; @@ -1764,7 +1764,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { // If we didn't return early here, we would instead suggest `&&str::from("")`. return false; } else if let hir::ExprKind::Call(_, args) = expr.kind { - if let Some(pred) = self + // The `def_id` can point at a struct, which has no fn sig. + if matches!( + self.tcx.def_kind(*def_id), + DefKind::AssocFn | DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) + ) && let Some(pred) = self .tcx .clauses_of(*def_id) .instantiate_identity(self.tcx) @@ -1799,6 +1803,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { c @ ObligationCauseCode::WhereClauseInExpr(def_id, _, hir_id, idx) if let hir::Node::Expr(expr) = self.tcx.hir_node(*hir_id) && let hir::ExprKind::MethodCall(_segment, rcvr, args, ..) = expr.kind + // The `def_id` can also point at the impl, which has no fn sig. + && matches!( + self.tcx.def_kind(*def_id), + DefKind::AssocFn | DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) + ) && let Some(pred) = self .tcx .clauses_of(*def_id) diff --git a/tests/ui/structs/ice-missing-field-fn-sig.rs b/tests/ui/structs/ice-missing-field-fn-sig.rs new file mode 100644 index 0000000000000..9ba48f6a6e795 --- /dev/null +++ b/tests/ui/structs/ice-missing-field-fn-sig.rs @@ -0,0 +1,14 @@ +// A struct literal that's missing fields shouldn't ICE when checking the fn sig. + +trait Context {} +struct Wrapper { + container: &'static C, +} +fn foobar(_: Wrapper<()>) { //~ ERROR the trait bound `(): Context` is not satisfied + foobar(Wrapper { /* missing */ }) +//~^ ERROR the trait bound `(): Context` is not satisfied +//~^^ ERROR missing field `container` in initializer of `Wrapper<_>` +//~^^^ ERROR the trait bound `(): Context` is not satisfied +} + +fn main() {} diff --git a/tests/ui/structs/ice-missing-field-fn-sig.stderr b/tests/ui/structs/ice-missing-field-fn-sig.stderr new file mode 100644 index 0000000000000..7be7886b2d975 --- /dev/null +++ b/tests/ui/structs/ice-missing-field-fn-sig.stderr @@ -0,0 +1,61 @@ +error[E0277]: the trait bound `(): Context` is not satisfied + --> $DIR/ice-missing-field-fn-sig.rs:7:14 + | +LL | fn foobar(_: Wrapper<()>) { + | ^^^^^^^^^^^ the trait `Context` is not implemented for `()` + | +help: this trait has no implementations, consider adding one + --> $DIR/ice-missing-field-fn-sig.rs:3:1 + | +LL | trait Context {} + | ^^^^^^^^^^^^^ +note: required by a bound in `Wrapper` + --> $DIR/ice-missing-field-fn-sig.rs:4:19 + | +LL | struct Wrapper { + | ^^^^^^^ required by this bound in `Wrapper` + +error[E0277]: the trait bound `(): Context` is not satisfied + --> $DIR/ice-missing-field-fn-sig.rs:8:12 + | +LL | foobar(Wrapper { /* missing */ }) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Context` is not implemented for `()` + | +help: this trait has no implementations, consider adding one + --> $DIR/ice-missing-field-fn-sig.rs:3:1 + | +LL | trait Context {} + | ^^^^^^^^^^^^^ +note: required by a bound in `Wrapper` + --> $DIR/ice-missing-field-fn-sig.rs:4:19 + | +LL | struct Wrapper { + | ^^^^^^^ required by this bound in `Wrapper` + +error[E0063]: missing field `container` in initializer of `Wrapper<_>` + --> $DIR/ice-missing-field-fn-sig.rs:8:12 + | +LL | foobar(Wrapper { /* missing */ }) + | ^^^^^^^ missing `container` + +error[E0277]: the trait bound `(): Context` is not satisfied + --> $DIR/ice-missing-field-fn-sig.rs:8:12 + | +LL | foobar(Wrapper { /* missing */ }) + | ^^^^^^^ the trait `Context` is not implemented for `()` + | +help: this trait has no implementations, consider adding one + --> $DIR/ice-missing-field-fn-sig.rs:3:1 + | +LL | trait Context {} + | ^^^^^^^^^^^^^ +note: required by a bound in `Wrapper` + --> $DIR/ice-missing-field-fn-sig.rs:4:19 + | +LL | struct Wrapper { + | ^^^^^^^ required by this bound in `Wrapper` + +error: aborting due to 4 previous errors + +Some errors have detailed explanations: E0063, E0277. +For more information about an error, try `rustc --explain E0063`. From 140d4cb35cba6c650533246a0c59549992e49b4f Mon Sep 17 00:00:00 2001 From: aerooneqq Date: Thu, 6 Aug 2026 10:56:10 +0000 Subject: [PATCH 064/100] delegation: add support for wrapping of the return value with `From::from` * Add support for wrapping of the return value of delegation * Cleanups * Review: use `make_lang_item_qpath` --- .../src/delegation/generics.rs | 4 +- .../rustc_ast_lowering/src/delegation/mod.rs | 11 +- .../src/delegation/resolution.rs | 51 ++++- compiler/rustc_hir/src/lang_items.rs | 1 + compiler/rustc_middle/src/ty/sty.rs | 9 + library/core/src/convert/mod.rs | 1 + .../pretty/delegation/self-mapping-output.pp | 4 +- .../self-mapping-output-from-wrap-errors.rs | 72 +++++++ ...elf-mapping-output-from-wrap-errors.stderr | 57 +++++ .../self-mapping-output-from-wrap.rs | 199 ++++++++++++++++++ .../self-mapping-output-from-wrap.run.stdout | 10 + 11 files changed, 405 insertions(+), 14 deletions(-) create mode 100644 tests/ui/delegation/self-mapping-output-from-wrap-errors.rs create mode 100644 tests/ui/delegation/self-mapping-output-from-wrap-errors.stderr create mode 100644 tests/ui/delegation/self-mapping-output-from-wrap.rs create mode 100644 tests/ui/delegation/self-mapping-output-from-wrap.run.stdout diff --git a/compiler/rustc_ast_lowering/src/delegation/generics.rs b/compiler/rustc_ast_lowering/src/delegation/generics.rs index 4d9bc09faeecb..911ec5956006d 100644 --- a/compiler/rustc_ast_lowering/src/delegation/generics.rs +++ b/compiler/rustc_ast_lowering/src/delegation/generics.rs @@ -662,10 +662,10 @@ impl<'hir> LoweringContext<'_, 'hir> { p.def_id.to_def_id(), ); - self.create_resolved_path(res, p.name.ident(), p.span) + self.create_resolved_qpath(res, p.name.ident(), p.span) } - pub(super) fn create_resolved_path( + pub(super) fn create_resolved_qpath( &mut self, res: Res, ident: Ident, diff --git a/compiler/rustc_ast_lowering/src/delegation/mod.rs b/compiler/rustc_ast_lowering/src/delegation/mod.rs index d0033ba0e472e..02fd6de314d3a 100644 --- a/compiler/rustc_ast_lowering/src/delegation/mod.rs +++ b/compiler/rustc_ast_lowering/src/delegation/mod.rs @@ -439,7 +439,7 @@ impl<'hir> LoweringContext<'_, 'hir> { }; let ident = Ident::new(kw::SelfUpper, span); - let path = self.create_resolved_path(res, ident, span); + let path = self.create_resolved_qpath(res, ident, span); // FIXME(fn_delegation): add default `..` for all other fields. let initializer = hir::ExprKind::Struct( @@ -454,7 +454,14 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::StructTailExpr::None, ); - self.arena.alloc(self.mk_expr(initializer, span)) + let expr = self.mk_expr(initializer, span); + + let path = self.make_lang_item_qpath(hir::LangItem::FromFn, span, None); + let path = self.arena.alloc(self.mk_expr(hir::ExprKind::Path(path), span)); + + let call = hir::ExprKind::Call(path, self.arena.alloc_slice(&[expr])); + + self.arena.alloc(self.mk_expr(call, span)) } else { self.arena.alloc(call) }; diff --git a/compiler/rustc_ast_lowering/src/delegation/resolution.rs b/compiler/rustc_ast_lowering/src/delegation/resolution.rs index 1d9bcef7ac5a7..dd1b9518e6d7f 100644 --- a/compiler/rustc_ast_lowering/src/delegation/resolution.rs +++ b/compiler/rustc_ast_lowering/src/delegation/resolution.rs @@ -5,10 +5,10 @@ use hir::def::DefKind; use rustc_ast::{self as ast, Delegation, DelegationSource, NodeId}; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_hir as hir; -use rustc_middle::ty::Ty; +use rustc_middle::ty::{Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor}; use rustc_middle::{span_bug, ty}; use rustc_span::def_id::{DefId, LocalDefId}; -use rustc_span::{ErrorGuaranteed, Span, kw}; +use rustc_span::{ErrorGuaranteed, Span}; use crate::delegation::generics::GenericsGenerationResults; use crate::delegation::resolution::resolver::DelegationResolver; @@ -31,7 +31,7 @@ pub(super) struct ParamInfo { pub splatted: Option, } -#[derive(Default)] +#[derive(Default, Debug)] pub(super) struct SigMapping { pub map_return: bool, pub arguments_to_map: FxIndexSet, @@ -254,17 +254,52 @@ impl<'tcx> DelegationResolver<'_, 'tcx> { } if self.can_perform_self_mapping(delegation, parent)? { - // FIXME(fn_delegation): support heuristics for mapping of complex - // return types: `Self` -> `Box>>` - mapping.map_return = sig.output().is_param(0); + /// Finds `Self` generic param only in ADT or references, so we avoid cases like + /// `Self::Item` which will return true if `output.contains(...)` will be used. + struct SelfFinder; + + impl<'tcx> TypeVisitor> for SelfFinder { + type Result = ControlFlow<()>; + + fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result { + match t.kind() { + ty::Adt(_, args) => { + if args + .iter() + .flat_map(|arg| arg.as_type()) + .any(|type_arg| type_arg.is_self_param()) + { + return ControlFlow::Break(()); + } + + t.super_visit_with(self) + } + ty::Ref(_, ref_t, _) => { + if ref_t.is_self_param() { + return ControlFlow::Break(()); + } + + t.super_visit_with(self) + } + _ => ControlFlow::Continue(()), + } + } + } + + impl SelfFinder { + fn contains_self(t: Ty<'_>) -> bool { + t.is_self_param() || t.visit_with(&mut SelfFinder).is_break() + } + } + + mapping.map_return = SelfFinder::contains_self(sig.output()); - let self_param = Ty::new_param(self.tcx(), 0, kw::SelfUpper); let arguments_to_map = sig .inputs() .iter() .enumerate() .skip(1) // Already checked above. - .filter_map(|(idx, param)| param.contains(self_param).then_some(idx)); + .filter_map(|(idx, ¶m)| SelfFinder::contains_self(param).then_some(idx)); mapping.arguments_to_map.extend(arguments_to_map); } diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index e6e0b3726552f..fe1ca75d30cf7 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -456,6 +456,7 @@ language_item_table! { // Used to fallback `{float}` to `f32` when `f32: From<{float}>` From, sym::From, from_trait, Target::Trait, GenericRequirement::Exact(1); + FromFn, sym::from, from_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; } /// The requirement imposed on the generics of a lang item diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index e768c75961937..0c0e3d87c9f20 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -1191,6 +1191,15 @@ impl<'tcx> Ty<'tcx> { matches!(self.kind(), Adt(..)) } + #[inline] + pub fn is_self_param(self) -> bool { + if let Param(param) = self.kind() { + param.index == 0 && param.name == kw::SelfUpper + } else { + false + } + } + #[inline] pub fn is_ref(self) -> bool { matches!(self.kind(), Ref(..)) diff --git a/library/core/src/convert/mod.rs b/library/core/src/convert/mod.rs index ae8458c199503..912623b73050e 100644 --- a/library/core/src/convert/mod.rs +++ b/library/core/src/convert/mod.rs @@ -591,6 +591,7 @@ pub const trait From: Sized { #[rustc_diagnostic_item = "from_fn"] #[must_use] #[stable(feature = "rust1", since = "1.0.0")] + #[lang = "from"] fn from(value: T) -> Self; } diff --git a/tests/pretty/delegation/self-mapping-output.pp b/tests/pretty/delegation/self-mapping-output.pp index 84e98d6e97b06..5bce43315e1d0 100644 --- a/tests/pretty/delegation/self-mapping-output.pp +++ b/tests/pretty/delegation/self-mapping-output.pp @@ -24,7 +24,7 @@ struct W(S); impl Trait for W { #[attr = Inline(Hint)] - fn method(self: _) -> _ { Self { 0: Trait::method(self.0) } } + fn method(self: _) -> _ { from(Self { 0: Trait::method(self.0) }) } #[attr = Inline(Hint)] fn r#static() -> _ { Trait::r#static() } //~^ WARN: function cannot return without recursing [unconditional_recursion] @@ -34,7 +34,7 @@ impl W { #[attr = Inline(Hint)] - fn method(self: _) -> _ { Self { 0: Trait::method(self.0) } } + fn method(self: _) -> _ { from(Self { 0: Trait::method(self.0) }) } #[attr = Inline(Hint)] fn r#static() -> _ { Trait::r#static() } #[attr = Inline(Hint)] diff --git a/tests/ui/delegation/self-mapping-output-from-wrap-errors.rs b/tests/ui/delegation/self-mapping-output-from-wrap-errors.rs new file mode 100644 index 0000000000000..6ef2a4b72559c --- /dev/null +++ b/tests/ui/delegation/self-mapping-output-from-wrap-errors.rs @@ -0,0 +1,72 @@ +#![feature(fn_delegation)] + +mod pin_box_self { + use std::pin::Pin; + + trait MyAdd { + fn add(self, other: Self) -> Pin>; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Pin> { + Pin::new(Box::new(self + other)) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Pin>); + + reuse impl MyAdd for W { + //~^ ERROR: the trait bound `Pin>: From` is not satisfied + *self.0 + } +} + +mod many_froms { + use std::sync::Arc; + use std::rc::Rc; + + trait MyAdd { + fn add(self, other: Self) -> Box>>>>>; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Box>>>>> { + Box::new(Box::new(Box::new(Arc::new(Box::new(Rc::new(self + other)))))) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Box>>>>>); + + reuse impl MyAdd for W { + //~^ ERROR: the trait bound `Box>>>>>: From` is not satisfied + ******self.0 + } +} + +mod many_froms_2 { + use std::sync::Arc; + use std::rc::Rc; + + trait MyAdd { + fn add(self, other: Self) -> Box>>>>; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Box>>>> { + Box::new(Arc::new(Rc::new(Box::new(Rc::new(self + other))))) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Box>>>>); + + reuse impl MyAdd for W { + //~^ ERROR: the trait bound `Box>>>>: From` is not satisfied + *****self.0 + } +} + +fn main() { +} diff --git a/tests/ui/delegation/self-mapping-output-from-wrap-errors.stderr b/tests/ui/delegation/self-mapping-output-from-wrap-errors.stderr new file mode 100644 index 0000000000000..d6290bc220966 --- /dev/null +++ b/tests/ui/delegation/self-mapping-output-from-wrap-errors.stderr @@ -0,0 +1,57 @@ +error[E0277]: the trait bound `Pin>: From` is not satisfied + --> $DIR/self-mapping-output-from-wrap-errors.rs:19:5 + | +LL | / reuse impl MyAdd for W { +LL | | +LL | | *self.0 +LL | | } + | |_____^ the trait `From` is not implemented for `Pin>` + | +help: the trait `From` is not implemented for `Pin>` + but trait `From>` is implemented for it + --> $SRC_DIR/alloc/src/boxed/convert.rs:LL:COL + = help: for that trait implementation, expected `Box`, found `pin_box_self::W` + +error[E0277]: the trait bound `Box>>>>>: From` is not satisfied + --> $DIR/self-mapping-output-from-wrap-errors.rs:42:5 + | +LL | / reuse impl MyAdd for W { +LL | | +LL | | ******self.0 +LL | | } + | |_____^ the trait `From` is not implemented for `Box>>>>>` + | + = help: the following other types implement trait `From`: + `Box` implements `From>` + `Box` implements `From<&CStr>` + `Box` implements `From<&mut CStr>` + `Box` implements `From` + `Box` implements `From>` + `Box` implements `From<&OsStr>` + `Box` implements `From<&mut OsStr>` + `Box` implements `From>` + and 25 others + +error[E0277]: the trait bound `Box>>>>: From` is not satisfied + --> $DIR/self-mapping-output-from-wrap-errors.rs:65:5 + | +LL | / reuse impl MyAdd for W { +LL | | +LL | | *****self.0 +LL | | } + | |_____^ the trait `From` is not implemented for `Box>>>>` + | + = help: the following other types implement trait `From`: + `Box` implements `From>` + `Box` implements `From<&CStr>` + `Box` implements `From<&mut CStr>` + `Box` implements `From` + `Box` implements `From>` + `Box` implements `From<&OsStr>` + `Box` implements `From<&mut OsStr>` + `Box` implements `From>` + and 25 others + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/delegation/self-mapping-output-from-wrap.rs b/tests/ui/delegation/self-mapping-output-from-wrap.rs new file mode 100644 index 0000000000000..2dfff2882fbc6 --- /dev/null +++ b/tests/ui/delegation/self-mapping-output-from-wrap.rs @@ -0,0 +1,199 @@ +//@ run-pass +//@ check-run-results + +#![feature(fn_delegation)] + +mod simple_self { + trait MyAdd { + fn add(self, other: Self) -> Self; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> usize { + self + other + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(usize); + + reuse impl MyAdd for W { + println!("simple_self {self:?}"); + self.0 + } + + pub fn check() { + assert_eq!(W(1).add(W(2)), W(3)) + } +} + +mod box_self { + trait MyAdd { + fn add(self, other: Self) -> Box; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Box { + Box::new(self + other) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Box); + + reuse impl MyAdd for W { + println!("box_self {self:?}"); + *self.0 + } + + pub fn check() { + fn w(x: usize) -> W { + W(Box::new(x)) + } + + assert_eq!(w(1).add(w(2)), Box::new(w(3))) + } +} + +mod rc_self { + use std::rc::Rc; + + trait MyAdd { + fn add(self, other: Self) -> Rc; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Rc { + Rc::new(self + other) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Rc); + + reuse impl MyAdd for W { + println!("rc_self {self:?}"); + *self.0 + } + + pub fn check() { + fn w(x: usize) -> W { + W(Rc::new(x)) + } + + assert_eq!(w(1).add(w(2)), Rc::new(w(3))) + } +} + +mod arc_self { + use std::sync::Arc; + + trait MyAdd { + fn add(self, other: Self) -> Arc; + } + + impl MyAdd for usize { + fn add(self, other: usize) -> Arc { + Arc::new(self + other) + } + } + + #[derive(Eq, PartialEq, Debug)] + struct W(Arc); + + reuse impl MyAdd for W { + println!("arc_self {self:?}"); + *self.0 + } + + pub fn check() { + fn w(x: usize) -> W { + W(Arc::new(x)) + } + + assert_eq!(w(1).add(w(2)), Arc::new(w(3))) + } +} + +mod custom_froms { + #[derive(Debug)] + struct S1 { + a: A, + } + + impl From for S1 { + fn from(a: A) -> S1 { + S1 { a } + } + } + + #[derive(Debug)] + struct S2 { + t: T, + } + + impl From for S2 { + fn from(t: T) -> S2 { + S2 { t } + } + } + + #[derive(Debug)] + struct S3<'a, const C: usize, T, U, const B: bool> { + t: T, + pd: std::marker::PhantomData<&'a [(usize, U); C]> + } + + impl<'a, const C: usize, T, const B: bool> From for S3<'a, C, T, (), B> { + fn from(t: T) -> S3<'a, C, T, (), B> { + S3 { + t, + pd: std::marker::PhantomData::<&'a [(usize, ()); C]>, + } + } + } + + trait MyAdd: Sized { + fn add(self, other: Self) -> S1>>, (), true>>>; + } + + fn create_monster_struct(x: T) -> S1>>, (), true>>> { + S1::from(S1::from(S3::from(S2::from(S2::from(S1::from(x)))))) + } + + impl MyAdd for usize { + fn add(self, other: usize) -> S1>>, (), true>>> { + create_monster_struct(self + other) + } + } + + #[derive(Debug)] + struct W(S1>>, (), true>>>); + + impl From for S1>>, (), true>>> { + fn from(x: W) -> Self { + create_monster_struct(x) + } + } + + reuse impl MyAdd for W { + println!("custom_froms {self:?}"); + self.0.a.a.t.t.t.a + } + + pub fn check() { + fn w(x: usize) -> W { + W(create_monster_struct(x)) + } + + assert_eq!(w(1).add(w(2)).a.a.t.t.t.a.0.a.a.t.t.t.a, 3) + } +} + +fn main() { + simple_self::check(); + box_self::check(); + rc_self::check(); + arc_self::check(); + custom_froms::check(); +} diff --git a/tests/ui/delegation/self-mapping-output-from-wrap.run.stdout b/tests/ui/delegation/self-mapping-output-from-wrap.run.stdout new file mode 100644 index 0000000000000..ee96199c54e07 --- /dev/null +++ b/tests/ui/delegation/self-mapping-output-from-wrap.run.stdout @@ -0,0 +1,10 @@ +simple_self W(1) +simple_self W(2) +box_self W(1) +box_self W(2) +rc_self W(1) +rc_self W(2) +arc_self W(1) +arc_self W(2) +custom_froms W(S1 { a: S1 { a: S3 { t: S2 { t: S2 { t: S1 { a: 1 } } }, pd: PhantomData<&[(usize, ()); 123]> } } }) +custom_froms W(S1 { a: S1 { a: S3 { t: S2 { t: S2 { t: S1 { a: 2 } } }, pd: PhantomData<&[(usize, ()); 123]> } } }) From dec94938eb6a69dd5437f68a02cd54c4626da132 Mon Sep 17 00:00:00 2001 From: Mohamed Ali Date: Thu, 6 Aug 2026 14:04:50 +0300 Subject: [PATCH 065/100] [Priroda] CI: add clippy check for priroda --- src/tools/miri/.github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml index 5ede5327b625e..8b7935fb994a2 100644 --- a/src/tools/miri/.github/workflows/ci.yml +++ b/src/tools/miri/.github/workflows/ci.yml @@ -171,6 +171,9 @@ jobs: - name: build Priroda working-directory: priroda run: cargo build --locked + - name: clippy Priroda + working-directory: priroda + run: cargo clippy --all-targets --locked -- -D warnings - name: test Priroda working-directory: priroda run: | From 86b915f4ddcfa4f9bd6f89ec6614a12c9d9eb897 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Wed, 5 Aug 2026 19:16:30 +0300 Subject: [PATCH 066/100] expand: Feature gate AST-based attribute macros on expressions and non-item statements --- compiler/rustc_expand/src/expand.rs | 1 + tests/ui/cfg/cfg-stmt-recovery.rs | 2 +- .../invalid-node-range-issue-129166.rs | 2 +- tests/ui/eii/errors.rs | 2 +- tests/ui/eii/errors.stderr | 13 ++++++++++++- tests/ui/macros/issue-111749.rs | 1 + tests/ui/macros/issue-111749.stderr | 13 ++++++++++++- tests/ui/proc-macro/cfg-eval-fail.rs | 1 + tests/ui/proc-macro/cfg-eval-fail.stderr | 13 ++++++++++++- .../ui/proc-macro/derive-macro-invalid-placement.rs | 2 +- 10 files changed, 43 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 045233c0c4d21..4846b48af8d5e 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -858,6 +858,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { Err(guar) => return ExpandResult::Ready(fragment_kind.dummy(span, guar)), } } else if let SyntaxExtensionKind::LegacyAttr(expander) = ext { + self.gate_proc_macro_attr_item(span, &item); // `LegacyAttr` is only used for builtin attribute macros, which have their // safety checked by `check_builtin_meta_item`, so we don't need to check // `unsafety` here. diff --git a/tests/ui/cfg/cfg-stmt-recovery.rs b/tests/ui/cfg/cfg-stmt-recovery.rs index f0f9a649165b5..98f79cd8cfc1c 100644 --- a/tests/ui/cfg/cfg-stmt-recovery.rs +++ b/tests/ui/cfg/cfg-stmt-recovery.rs @@ -1,7 +1,7 @@ // Verify that we do not ICE when failing to parse a statement in `cfg_eval`. #![feature(cfg_eval)] -#![feature(stmt_expr_attributes)] +#![feature(stmt_expr_attributes, proc_macro_hygiene)] #[cfg_eval] fn main() { diff --git a/tests/ui/conditional-compilation/invalid-node-range-issue-129166.rs b/tests/ui/conditional-compilation/invalid-node-range-issue-129166.rs index 7c42be3ed4d6e..3f6f902cf3688 100644 --- a/tests/ui/conditional-compilation/invalid-node-range-issue-129166.rs +++ b/tests/ui/conditional-compilation/invalid-node-range-issue-129166.rs @@ -3,7 +3,7 @@ //@ check-pass #![feature(cfg_eval)] -#![feature(stmt_expr_attributes)] +#![feature(stmt_expr_attributes, proc_macro_hygiene)] fn f() -> u32 { #[cfg_eval] #[cfg(not(FALSE))] 0 diff --git a/tests/ui/eii/errors.rs b/tests/ui/eii/errors.rs index bc6c17f463a78..3b28e268662ef 100644 --- a/tests/ui/eii/errors.rs +++ b/tests/ui/eii/errors.rs @@ -8,7 +8,7 @@ #[eii_declaration(bar)] //~ ERROR `#[eii_declaration(...)]` is only valid on macros fn hello() { #[eii_declaration(bar)] //~ ERROR `#[eii_declaration(...)]` is only valid on macros - let x = 3 + 3; + let x = 3 + 3; //~| ERROR custom attributes cannot be applied to statements } #[eii_declaration] //~ ERROR `#[eii_declaration(...)]` expects a list of one or two elements diff --git a/tests/ui/eii/errors.stderr b/tests/ui/eii/errors.stderr index 553ae622cb36f..512cd135de4c3 100644 --- a/tests/ui/eii/errors.stderr +++ b/tests/ui/eii/errors.stderr @@ -4,6 +4,16 @@ error: `#[eii_declaration(...)]` is only valid on macros LL | #[eii_declaration(bar)] | ^^^^^^^^^^^^^^^^^^^^^^^ +error[E0658]: custom attributes cannot be applied to statements + --> $DIR/errors.rs:10:5 + | +LL | #[eii_declaration(bar)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: see issue #54727 for more information + = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error: `#[eii_declaration(...)]` is only valid on macros --> $DIR/errors.rs:10:5 | @@ -88,5 +98,6 @@ error: `#[foo]` expected no arguments or a single argument: `#[foo(default)]` LL | #[foo = "default"] | ^^^^^^^^^^^^^^^^^^ -error: aborting due to 14 previous errors +error: aborting due to 15 previous errors +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/macros/issue-111749.rs b/tests/ui/macros/issue-111749.rs index f009a69fe2535..799fee22685ab 100644 --- a/tests/ui/macros/issue-111749.rs +++ b/tests/ui/macros/issue-111749.rs @@ -9,4 +9,5 @@ fn main() { //~^ ERROR the `test` attribute may only be used on a free function //~| ERROR attribute must be of the form `#[test]` //~| WARNING this was previously accepted by the compiler but is being phased out + //~| ERROR custom attributes cannot be applied to expressions } diff --git a/tests/ui/macros/issue-111749.stderr b/tests/ui/macros/issue-111749.stderr index 267f939602b5b..f2773e7029ab5 100644 --- a/tests/ui/macros/issue-111749.stderr +++ b/tests/ui/macros/issue-111749.stderr @@ -1,3 +1,13 @@ +error[E0658]: custom attributes cannot be applied to expressions + --> $DIR/issue-111749.rs:8:17 + | +LL | cbor_map! { #[test(test)] 4i32}; + | ^^^^^^^^^^^^^ + | + = note: see issue #54727 for more information + = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + error: the `test` attribute may only be used on a free function --> $DIR/issue-111749.rs:8:17 | @@ -20,8 +30,9 @@ LL | cbor_map! { #[test(test)] 4i32}; = note: for more information, see issue #57571 = note: `#[deny(ill_formed_attribute_input)]` (part of `#[deny(future_incompatible)]`) on by default -error: aborting due to 2 previous errors +error: aborting due to 3 previous errors +For more information about this error, try `rustc --explain E0658`. Future incompatibility report: Future breakage diagnostic: error: attribute must be of the form `#[test]` --> $DIR/issue-111749.rs:8:17 diff --git a/tests/ui/proc-macro/cfg-eval-fail.rs b/tests/ui/proc-macro/cfg-eval-fail.rs index a94dcd2837811..2cde895f2ea44 100644 --- a/tests/ui/proc-macro/cfg-eval-fail.rs +++ b/tests/ui/proc-macro/cfg-eval-fail.rs @@ -4,4 +4,5 @@ fn main() { let _ = #[cfg_eval] #[cfg(false)] 0; //~^ ERROR removing an expression is not supported in this position + //~| ERROR custom attributes cannot be applied to expressions } diff --git a/tests/ui/proc-macro/cfg-eval-fail.stderr b/tests/ui/proc-macro/cfg-eval-fail.stderr index 7f21e4646b1cc..61da346fa69f6 100644 --- a/tests/ui/proc-macro/cfg-eval-fail.stderr +++ b/tests/ui/proc-macro/cfg-eval-fail.stderr @@ -4,5 +4,16 @@ error: removing an expression is not supported in this position LL | let _ = #[cfg_eval] #[cfg(false)] 0; | ^^^^^^^^^^^^^ -error: aborting due to 1 previous error +error[E0658]: custom attributes cannot be applied to expressions + --> $DIR/cfg-eval-fail.rs:5:13 + | +LL | let _ = #[cfg_eval] #[cfg(false)] 0; + | ^^^^^^^^^^^ + | + = note: see issue #54727 for more information + = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 2 previous errors +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/proc-macro/derive-macro-invalid-placement.rs b/tests/ui/proc-macro/derive-macro-invalid-placement.rs index fd24bd7284a92..463e7dc758505 100644 --- a/tests/ui/proc-macro/derive-macro-invalid-placement.rs +++ b/tests/ui/proc-macro/derive-macro-invalid-placement.rs @@ -1,6 +1,6 @@ //! regression test for -#![feature(stmt_expr_attributes)] +#![feature(stmt_expr_attributes, proc_macro_hygiene)] fn foo<#[derive(Debug)] T>() { //~ ERROR expected non-macro attribute, found attribute macro match 0 { From f2dd93228abcf29ab85960457df7a6f828eb3cb9 Mon Sep 17 00:00:00 2001 From: Marius Melzer Date: Mon, 13 Jul 2026 11:39:36 +0200 Subject: [PATCH 067/100] L4Re: Repair build and move to rustc linking Fixes the builds of rustc and library/std for the L4Re target OS. A major change was done in linking binaries: The need for the L4Bender tool was removed and linking parameters are now fully configured in the rustc target config. --- compiler/rustc_codegen_ssa/src/back/linker.rs | 126 ------- compiler/rustc_codegen_ssa/src/diagnostics.rs | 4 - compiler/rustc_target/src/spec/base/l4re.rs | 55 ++- .../targets/x86_64_unknown_l4re_uclibc.rs | 6 +- library/panic_unwind/src/lib.rs | 5 - library/std/src/fs.rs | 1 + library/std/src/fs/tests.rs | 8 +- library/std/src/net/ip_addr.rs | 9 +- library/std/src/net/mod.rs | 2 +- library/std/src/net/socket_addr.rs | 9 +- library/std/src/net/tcp.rs | 1 + library/std/src/net/udp.rs | 1 + library/std/src/os/fd/mod.rs | 1 + library/std/src/os/fd/raw.rs | 2 +- library/std/src/os/l4re/fs.rs | 50 +-- library/std/src/os/l4re/raw.rs | 349 +----------------- library/std/src/os/unix/fs.rs | 1 + library/std/src/os/unix/net/mod.rs | 2 +- library/std/src/process.rs | 1 + library/std/src/process/tests.rs | 98 ++++- library/std/src/random.rs | 2 +- library/std/src/sys/fs/unix.rs | 28 +- library/std/src/sys/io/error/unix.rs | 3 +- library/std/src/sys/net/connection/mod.rs | 2 +- .../std/src/sys/net/connection/socket/mod.rs | 1 + library/std/src/sys/pal/unix/mod.rs | 30 +- library/std/src/sys/personality/mod.rs | 2 +- library/std/src/sys/process/mod.rs | 6 +- library/std/src/sys/process/unix/common.rs | 17 +- .../std/src/sys/process/unix/common/tests.rs | 10 + library/std/src/sys/process/unix/mod.rs | 7 +- .../std/src/sys/process/unix/unix/tests.rs | 5 +- .../std/src/sys/process/unix/unsupported.rs | 2 +- library/std/src/sys/random/mod.rs | 3 +- library/std/src/thread/functions.rs | 9 +- library/std/tests/env.rs | 5 +- library/std/tests/pipe_subprocess.rs | 8 +- library/std/tests/process_spawning.rs | 5 +- library/std/tests/time.rs | 1 + library/unwind/src/lib.rs | 2 +- src/bootstrap/src/utils/helpers.rs | 3 +- 41 files changed, 299 insertions(+), 583 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs index 50a3e7fb7a1d1..135faa5817516 100644 --- a/compiler/rustc_codegen_ssa/src/back/linker.rs +++ b/compiler/rustc_codegen_ssa/src/back/linker.rs @@ -137,9 +137,6 @@ pub(crate) fn get_linker<'a>( // to the linker args construction. assert!(cmd.get_args().is_empty() || sess.target.cfg_abi == CfgAbi::Uwp); match flavor { - LinkerFlavor::Unix(Cc::No) if sess.target.os == Os::L4Re => { - Box::new(L4Bender::new(cmd, sess)) as Box - } LinkerFlavor::Unix(Cc::No) if sess.target.os == Os::Aix => { Box::new(AixLinker::new(cmd, sess)) as Box } @@ -279,7 +276,6 @@ generate_arg_methods! { MsvcLinker<'_> EmLinker<'_> WasmLd<'_> - L4Bender<'_> AixLinker<'_> LlbcLinker<'_> BpfLinker<'_> @@ -1468,128 +1464,6 @@ impl<'a> WasmLd<'a> { } } -/// Linker shepherd script for L4Re (Fiasco) -struct L4Bender<'a> { - cmd: Command, - sess: &'a Session, - hinted_static: bool, -} - -impl<'a> Linker for L4Bender<'a> { - fn cmd(&mut self) -> &mut Command { - &mut self.cmd - } - - fn set_output_kind( - &mut self, - _output_kind: LinkOutputKind, - _crate_type: CrateType, - _out_filename: &Path, - ) { - } - - fn link_staticlib_by_name(&mut self, name: &str, _verbatim: bool, whole_archive: bool) { - self.hint_static(); - if !whole_archive { - self.link_arg(format!("-PC{name}")); - } else { - self.link_arg("--whole-archive") - .link_or_cc_arg(format!("-l{name}")) - .link_arg("--no-whole-archive"); - } - } - - fn link_staticlib_by_path(&mut self, path: &Path, whole_archive: bool) { - self.hint_static(); - if !whole_archive { - self.link_or_cc_arg(path); - } else { - self.link_arg("--whole-archive").link_or_cc_arg(path).link_arg("--no-whole-archive"); - } - } - - fn full_relro(&mut self) { - self.link_args(&["-z", "relro", "-z", "now"]); - } - - fn partial_relro(&mut self) { - self.link_args(&["-z", "relro"]); - } - - fn no_relro(&mut self) { - self.link_args(&["-z", "norelro"]); - } - - fn gc_sections(&mut self, keep_metadata: bool) { - if !keep_metadata { - self.link_arg("--gc-sections"); - } - } - - fn optimize(&mut self) { - // GNU-style linkers support optimization with -O. GNU ld doesn't - // need a numeric argument, but other linkers do. - if self.sess.opts.optimize == config::OptLevel::More - || self.sess.opts.optimize == config::OptLevel::Aggressive - { - self.link_arg("-O1"); - } - } - - fn pgo_gen(&mut self) {} - - fn debuginfo(&mut self, strip: Strip, _: &[PathBuf]) { - match strip { - Strip::None => {} - Strip::Debuginfo => { - self.link_arg("--strip-debug"); - } - Strip::Symbols => { - self.link_arg("--strip-all"); - } - } - } - - fn no_default_libraries(&mut self) { - self.cc_arg("-nostdlib"); - } - - fn export_symbols(&mut self, _: &Path, _: CrateType, _: &[SymbolExport]) { - // ToDo, not implemented, copy from GCC - self.sess.dcx().emit_warn(diagnostics::L4BenderExportingSymbolsUnimplemented); - } - - fn windows_subsystem(&mut self, subsystem: WindowsSubsystemKind) { - let subsystem = subsystem.as_str(); - self.link_arg(&format!("--subsystem {subsystem}")); - } - - fn reset_per_library_state(&mut self) { - self.hint_static(); // Reset to default before returning the composed command line. - } - - fn linker_plugin_lto(&mut self) {} - - fn control_flow_guard(&mut self) {} - - fn ehcont_guard(&mut self) {} - - fn no_crt_objects(&mut self) {} -} - -impl<'a> L4Bender<'a> { - fn new(cmd: Command, sess: &'a Session) -> L4Bender<'a> { - L4Bender { cmd, sess, hinted_static: false } - } - - fn hint_static(&mut self) { - if !self.hinted_static { - self.link_or_cc_arg("-static"); - self.hinted_static = true; - } - } -} - /// Linker for AIX. struct AixLinker<'a> { cmd: Command, diff --git a/compiler/rustc_codegen_ssa/src/diagnostics.rs b/compiler/rustc_codegen_ssa/src/diagnostics.rs index e6aab553072f2..fbe78b7e78503 100644 --- a/compiler/rustc_codegen_ssa/src/diagnostics.rs +++ b/compiler/rustc_codegen_ssa/src/diagnostics.rs @@ -97,10 +97,6 @@ pub(crate) struct Ld64UnimplementedModifier; #[diag("`as-needed` modifier not supported for current linker")] pub(crate) struct LinkerUnsupportedModifier; -#[derive(Diagnostic)] -#[diag("exporting symbols not implemented yet for L4Bender")] -pub(crate) struct L4BenderExportingSymbolsUnimplemented; - #[derive(Diagnostic)] #[diag("error enumerating natvis directory: {$error}")] pub(crate) struct NoNatvisDirectory { diff --git a/compiler/rustc_target/src/spec/base/l4re.rs b/compiler/rustc_target/src/spec/base/l4re.rs index 8722c8a71e23a..cc67bd6d3a487 100644 --- a/compiler/rustc_target/src/spec/base/l4re.rs +++ b/compiler/rustc_target/src/spec/base/l4re.rs @@ -1,14 +1,59 @@ -use crate::spec::{Cc, Env, LinkerFlavor, Os, PanicStrategy, RelocModel, TargetOptions, cvs}; +use crate::spec::{ + Cc, Env, LinkOutputKind, LinkSelfContainedComponents, LinkSelfContainedDefault, LinkerFlavor, + Os, PanicStrategy, TargetOptions, add_link_args, crt_objects, cvs, +}; pub(crate) fn opts() -> TargetOptions { + // add ld- and cc-style args + macro_rules! prepare_args { + ($($val:expr),+) => {{ + let ld_args = &[$($val),+]; + let cc_args = &[$(concat!("-Wl,", $val)),+]; + + let mut ret = TargetOptions::link_args(LinkerFlavor::Unix(Cc::No), ld_args); + add_link_args(&mut ret, LinkerFlavor::Unix(Cc::Yes), cc_args); + ret + }}; + } + + let pre_link_args = prepare_args!("-nostdlib", "-dynamic-linker=rom/libld-l4.so"); + + let late_link_args = prepare_args!("-lc", "-lgcc_eh"); + + let pre_link_objects_self_contained = crt_objects::new(&[ + (LinkOutputKind::StaticNoPicExe, &["crt1.o", "crti.o", "crtbeginT.o"]), + (LinkOutputKind::StaticPicExe, &["crt1.p.o", "crti.o", "crtbegin.o"]), + (LinkOutputKind::DynamicNoPicExe, &["crt1.o", "crti.o", "crtbegin.o"]), + (LinkOutputKind::DynamicPicExe, &["crt1.s.o", "crti.o", "crtbeginS.o"]), + (LinkOutputKind::DynamicDylib, &["crti.s.o", "crtbeginS.o"]), + (LinkOutputKind::StaticDylib, &["crti.s.o", "crtbeginS.o"]), + ]); + + let post_link_objects_self_contained = crt_objects::new(&[ + (LinkOutputKind::StaticNoPicExe, &["crtendT.o", "crtn.o"]), + (LinkOutputKind::StaticPicExe, &["crtend.o", "crtn.o"]), + (LinkOutputKind::DynamicNoPicExe, &["crtend.o", "crtn.o"]), + (LinkOutputKind::DynamicPicExe, &["crtendS.o", "crtn.o"]), + (LinkOutputKind::DynamicDylib, &["crtendS.o", "crtn.s.o"]), + (LinkOutputKind::StaticDylib, &["crtendS.o", "crtn.s.o"]), + ]); + TargetOptions { os: Os::L4Re, env: Env::Uclibc, - linker_flavor: LinkerFlavor::Unix(Cc::No), - panic_strategy: PanicStrategy::Abort, - linker: Some("l4-bender".into()), families: cvs!["unix"], - relocation_model: RelocModel::Static, + panic_strategy: PanicStrategy::Abort, + linker_flavor: LinkerFlavor::Unix(Cc::No), + dynamic_linking: true, + position_independent_executables: true, + has_thread_local: true, + pre_link_args, + late_link_args, + pre_link_objects_self_contained, + post_link_objects_self_contained, + link_self_contained: LinkSelfContainedDefault::WithComponents( + LinkSelfContainedComponents::LIBC | LinkSelfContainedComponents::CRT_OBJECTS, + ), ..Default::default() } } diff --git a/compiler/rustc_target/src/spec/targets/x86_64_unknown_l4re_uclibc.rs b/compiler/rustc_target/src/spec/targets/x86_64_unknown_l4re_uclibc.rs index 5ab6b094dfa06..7030a98305a0b 100644 --- a/compiler/rustc_target/src/spec/targets/x86_64_unknown_l4re_uclibc.rs +++ b/compiler/rustc_target/src/spec/targets/x86_64_unknown_l4re_uclibc.rs @@ -1,11 +1,13 @@ -use crate::spec::{Arch, PanicStrategy, Target, TargetMetadata, base}; +use crate::spec::{Arch, Cc, LinkerFlavor, Target, TargetMetadata, base}; pub(crate) fn target() -> Target { let mut base = base::l4re::opts(); base.cpu = "x86-64".into(); base.plt_by_default = false; base.max_atomic_width = Some(64); - base.panic_strategy = PanicStrategy::Abort; + let extra_link_args = &["-zmax-page-size=0x1000", "-zcommon-page-size=0x1000"]; + base.add_pre_link_args(LinkerFlavor::Unix(Cc::Yes), extra_link_args); + base.add_pre_link_args(LinkerFlavor::Unix(Cc::No), extra_link_args); Target { llvm_target: "x86_64-unknown-l4re-gnu".into(), diff --git a/library/panic_unwind/src/lib.rs b/library/panic_unwind/src/lib.rs index 9d204a150dd45..1644a2495d97e 100644 --- a/library/panic_unwind/src/lib.rs +++ b/library/panic_unwind/src/lib.rs @@ -36,11 +36,6 @@ cfg_select! { #[path = "hermit.rs"] mod imp; } - target_os = "l4re" => { - // L4Re is unix family but does not yet support unwinding. - #[path = "dummy.rs"] - mod imp; - } any( all(target_family = "windows", target_env = "gnu"), target_os = "psp", diff --git a/library/std/src/fs.rs b/library/std/src/fs.rs index 4c5cd0e0c9e6a..3b8499758d90d 100644 --- a/library/std/src/fs.rs +++ b/library/std/src/fs.rs @@ -37,6 +37,7 @@ target_env = "sgx", target_os = "xous", target_os = "trusty", + target_os = "l4re", )) ))] mod tests; diff --git a/library/std/src/fs/tests.rs b/library/std/src/fs/tests.rs index 1b069f2e77f6b..2a656e2f8c196 100644 --- a/library/std/src/fs/tests.rs +++ b/library/std/src/fs/tests.rs @@ -962,6 +962,10 @@ fn recursive_mkdir_slash() { } #[test] +#[cfg_attr( + target_os = "l4re", + ignore = "Path '.' in the file system root can not be resolved in L4Re" +)] fn recursive_mkdir_dot() { check!(fs::create_dir_all(Path::new("."))); } @@ -2117,6 +2121,7 @@ fn rename_directory() { } #[test] +#[cfg_attr(target_os = "l4re", ignore = "futimens")] fn test_file_times() { #[cfg(target_vendor = "apple")] use crate::os::darwin::fs::FileTimesExt; @@ -2145,7 +2150,8 @@ fn test_file_times() { target_os = "android", target_os = "redox", target_os = "espidf", - target_os = "horizon" + target_os = "horizon", + target_os = "l4re", )) ) )))] diff --git a/library/std/src/net/ip_addr.rs b/library/std/src/net/ip_addr.rs index 7262899b3bbbe..6bd78de910fec 100644 --- a/library/std/src/net/ip_addr.rs +++ b/library/std/src/net/ip_addr.rs @@ -1,5 +1,12 @@ // Tests for this module -#[cfg(all(test, not(any(target_os = "emscripten", all(target_os = "wasi", target_env = "p1")))))] +#[cfg(all( + test, + not(any( + target_os = "emscripten", + all(target_os = "wasi", target_env = "p1"), + target_os = "l4re" + )) +))] mod tests; #[stable(feature = "ip_addr", since = "1.7.0")] diff --git a/library/std/src/net/mod.rs b/library/std/src/net/mod.rs index 2a8b0f8ca9aad..1b1096925dd4a 100644 --- a/library/std/src/net/mod.rs +++ b/library/std/src/net/mod.rs @@ -42,7 +42,7 @@ mod hostname; mod ip_addr; mod socket_addr; mod tcp; -#[cfg(test)] +#[cfg(all(test, not(target_os = "l4re")))] pub(crate) mod tests; mod udp; diff --git a/library/std/src/net/socket_addr.rs b/library/std/src/net/socket_addr.rs index cae14e34e73e7..2dab8c26f1f6b 100644 --- a/library/std/src/net/socket_addr.rs +++ b/library/std/src/net/socket_addr.rs @@ -1,5 +1,12 @@ // Tests for this module -#[cfg(all(test, not(any(target_os = "emscripten", all(target_os = "wasi", target_env = "p1")))))] +#[cfg(all( + test, + not(any( + target_os = "emscripten", + all(target_os = "wasi", target_env = "p1"), + target_os = "l4re" + )) +))] mod tests; #[stable(feature = "rust1", since = "1.0.0")] diff --git a/library/std/src/net/tcp.rs b/library/std/src/net/tcp.rs index d9090320bd5a6..4ba4c4e8caa4c 100644 --- a/library/std/src/net/tcp.rs +++ b/library/std/src/net/tcp.rs @@ -7,6 +7,7 @@ all(target_os = "wasi", target_env = "p1"), target_os = "xous", target_os = "trusty", + target_os = "l4re", )) ))] mod tests; diff --git a/library/std/src/net/udp.rs b/library/std/src/net/udp.rs index cd925b9bdfdf8..4aa77fc9c1fe9 100644 --- a/library/std/src/net/udp.rs +++ b/library/std/src/net/udp.rs @@ -6,6 +6,7 @@ target_env = "sgx", target_os = "xous", target_os = "trusty", + target_os = "l4re", )) ))] mod tests; diff --git a/library/std/src/os/fd/mod.rs b/library/std/src/os/fd/mod.rs index 473d7ae3e2ae6..735f1cf8925fb 100644 --- a/library/std/src/os/fd/mod.rs +++ b/library/std/src/os/fd/mod.rs @@ -20,6 +20,7 @@ mod net; mod stdio; #[cfg(test)] +#[cfg(not(target_os = "l4re"))] mod tests; // Export the types and traits for the public API. diff --git a/library/std/src/os/fd/raw.rs b/library/std/src/os/fd/raw.rs index 0d96958b6cca1..a0c96e2836fc5 100644 --- a/library/std/src/os/fd/raw.rs +++ b/library/std/src/os/fd/raw.rs @@ -16,7 +16,7 @@ use crate::io; use crate::os::hermit::io::OwnedFd; #[cfg(all(not(target_os = "hermit"), not(target_os = "motor")))] use crate::os::raw; -#[cfg(all(doc, not(any(target_arch = "wasm32", target_env = "sgx"))))] +#[cfg(all(doc, not(any(target_arch = "wasm32", target_env = "sgx", target_os = "l4re"))))] use crate::os::unix::io::AsFd; #[cfg(unix)] use crate::os::unix::io::OwnedFd; diff --git a/library/std/src/os/l4re/fs.rs b/library/std/src/os/l4re/fs.rs index 491e04a4d25cf..2dc899bcb5a6e 100644 --- a/library/std/src/os/l4re/fs.rs +++ b/library/std/src/os/l4re/fs.rs @@ -21,7 +21,7 @@ pub trait MetadataExt { /// Unix platforms. The `os::unix::fs::MetadataExt` trait contains the /// cross-Unix abstractions contained within the raw stat. /// - /// [`stat`]: struct@crate::os::linux::raw::stat + /// [`stat`]: struct@crate::os::l4re::raw::stat /// /// # Examples /// @@ -29,7 +29,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -50,7 +50,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -68,7 +68,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -86,7 +86,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -104,7 +104,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -122,7 +122,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -140,7 +140,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -158,7 +158,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -179,7 +179,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -197,7 +197,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -217,7 +217,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -235,7 +235,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -255,7 +255,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -273,7 +273,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -293,7 +293,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -311,7 +311,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -329,7 +329,7 @@ pub trait MetadataExt { #[cfg_attr(not(target_os = "l4re"), doc = "```ignore (needs l4re)")] /// use std::fs; /// use std::io; - /// use std::os::linux::fs::MetadataExt; + /// use std::os::l4re::fs::MetadataExt; /// /// fn main() -> io::Result<()> { /// let meta = fs::metadata("some_file")?; @@ -345,7 +345,7 @@ pub trait MetadataExt { impl MetadataExt for Metadata { #[allow(deprecated)] fn as_raw_stat(&self) -> &raw::stat { - unsafe { &*(self.as_inner().as_inner() as *const libc::stat64 as *const raw::stat) } + unsafe { &*(self.as_inner().as_inner() as *const _ as *const raw::stat) } } fn st_dev(&self) -> u64 { self.as_inner().as_inner().st_dev as u64 @@ -372,22 +372,22 @@ impl MetadataExt for Metadata { self.as_inner().as_inner().st_size as u64 } fn st_atime(&self) -> i64 { - self.as_inner().as_inner().st_atime as i64 + self.as_inner().as_inner().st_atim.tv_sec as i64 } fn st_atime_nsec(&self) -> i64 { - self.as_inner().as_inner().st_atime_nsec as i64 + self.as_inner().as_inner().st_atim.tv_nsec as i64 } fn st_mtime(&self) -> i64 { - self.as_inner().as_inner().st_mtime as i64 + self.as_inner().as_inner().st_mtim.tv_sec as i64 } fn st_mtime_nsec(&self) -> i64 { - self.as_inner().as_inner().st_mtime_nsec as i64 + self.as_inner().as_inner().st_mtim.tv_nsec as i64 } fn st_ctime(&self) -> i64 { - self.as_inner().as_inner().st_ctime as i64 + self.as_inner().as_inner().st_ctim.tv_sec as i64 } fn st_ctime_nsec(&self) -> i64 { - self.as_inner().as_inner().st_ctime_nsec as i64 + self.as_inner().as_inner().st_ctim.tv_nsec as i64 } fn st_blksize(&self) -> u64 { self.as_inner().as_inner().st_blksize as u64 diff --git a/library/std/src/os/l4re/raw.rs b/library/std/src/os/l4re/raw.rs index 8fb6e99ecfa1e..f41fff015cab6 100644 --- a/library/std/src/os/l4re/raw.rs +++ b/library/std/src/os/l4re/raw.rs @@ -10,355 +10,14 @@ )] #![allow(deprecated)] -use crate::os::raw::c_ulong; - #[stable(feature = "raw_ext", since = "1.1.0")] -pub type dev_t = u64; +pub type dev_t = libc::dev_t; #[stable(feature = "raw_ext", since = "1.1.0")] -pub type mode_t = u32; +pub type mode_t = libc::mode_t; #[stable(feature = "pthread_t", since = "1.8.0")] -pub type pthread_t = c_ulong; +pub type pthread_t = libc::pthread_t; #[doc(inline)] #[stable(feature = "raw_ext", since = "1.1.0")] -pub use self::arch::{blkcnt_t, blksize_t, ino_t, nlink_t, off_t, stat, time_t}; - -#[cfg(any( - target_arch = "x86", - target_arch = "m68k", - target_arch = "csky", - target_arch = "powerpc", - target_arch = "sparc", - target_arch = "arm", - target_arch = "wasm32" -))] -mod arch { - use crate::os::raw::{c_long, c_short, c_uint}; - - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type nlink_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = i64; - - #[repr(C)] - #[derive(Clone)] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub struct stat { - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_dev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad1: c_short, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __st_ino: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mode: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_nlink: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_uid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_gid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_rdev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad2: c_uint, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_size: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blksize: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blocks: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ino: u64, - } -} - -#[cfg(target_arch = "mips")] -mod arch { - use crate::os::raw::{c_long, c_ulong}; - - #[cfg(target_env = "musl")] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = i64; - #[cfg(not(target_env = "musl"))] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = u64; - #[cfg(target_env = "musl")] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[cfg(not(target_env = "musl"))] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type nlink_t = u64; - #[cfg(target_env = "musl")] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = u64; - #[cfg(not(target_env = "musl"))] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = i64; - - #[repr(C)] - #[derive(Clone)] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub struct stat { - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_dev: c_ulong, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_pad1: [c_long; 3], - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ino: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mode: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_nlink: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_uid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_gid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_rdev: c_ulong, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_pad2: [c_long; 2], - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_size: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blksize: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blocks: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_pad5: [c_long; 14], - } -} - -#[cfg(target_arch = "hexagon")] -mod arch { - use crate::os::raw::{c_int, c_long, c_uint}; - - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = i64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = c_long; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type nlink_t = c_uint; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = i64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = i64; - - #[repr(C)] - #[derive(Clone)] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub struct stat { - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_dev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ino: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mode: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_nlink: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_uid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_gid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_rdev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad1: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_size: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blksize: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad2: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blocks: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad3: [c_int; 2], - } -} - -#[cfg(any( - target_arch = "mips64", - target_arch = "s390x", - target_arch = "sparc64", - target_arch = "riscv64", - target_arch = "riscv32" -))] -mod arch { - pub use libc::{blkcnt_t, blksize_t, ino_t, nlink_t, off_t, stat, time_t}; -} - -#[cfg(target_arch = "aarch64")] -mod arch { - use crate::os::raw::{c_int, c_long}; - - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = i64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = i32; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type nlink_t = u32; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = i64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = c_long; - - #[repr(C)] - #[derive(Clone)] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub struct stat { - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_dev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ino: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mode: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_nlink: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_uid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_gid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_rdev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad1: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_size: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blksize: i32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad2: c_int, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blocks: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime: time_t, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime: time_t, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime: time_t, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __unused: [c_int; 2], - } -} - -#[cfg(any(target_arch = "x86_64", target_arch = "powerpc64"))] -mod arch { - use crate::os::raw::{c_int, c_long}; - - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blkcnt_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type blksize_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type ino_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type nlink_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type off_t = u64; - #[stable(feature = "raw_ext", since = "1.1.0")] - pub type time_t = i64; - - #[repr(C)] - #[derive(Clone)] - #[stable(feature = "raw_ext", since = "1.1.0")] - pub struct stat { - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_dev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ino: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_nlink: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mode: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_uid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_gid: u32, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __pad0: c_int, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_rdev: u64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_size: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blksize: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_blocks: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_atime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_mtime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime: i64, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub st_ctime_nsec: c_long, - #[stable(feature = "raw_ext", since = "1.1.0")] - pub __unused: [c_long; 3], - } -} +pub use libc::{blkcnt_t, blksize_t, ino_t, nlink_t, off_t, stat, time_t}; diff --git a/library/std/src/os/unix/fs.rs b/library/std/src/os/unix/fs.rs index 90ad137dac178..aa604aabaffce 100644 --- a/library/std/src/os/unix/fs.rs +++ b/library/std/src/os/unix/fs.rs @@ -18,6 +18,7 @@ use crate::sys::{AsInner, AsInnerMut, FromInner}; use crate::{io, sys}; // Tests for this module +#[cfg(not(target_os = "l4re"))] #[cfg(test)] mod tests; diff --git a/library/std/src/os/unix/net/mod.rs b/library/std/src/os/unix/net/mod.rs index 137088dd832f7..92d2696a5ef43 100644 --- a/library/std/src/os/unix/net/mod.rs +++ b/library/std/src/os/unix/net/mod.rs @@ -10,7 +10,7 @@ mod ancillary; mod datagram; mod listener; mod stream; -#[cfg(all(test, not(target_os = "emscripten")))] +#[cfg(all(test, not(any(target_os = "emscripten", target_os = "l4re"))))] mod tests; #[cfg(any( target_os = "android", diff --git a/library/std/src/process.rs b/library/std/src/process.rs index c5ffbbc666e43..a398363cf4bf9 100644 --- a/library/std/src/process.rs +++ b/library/std/src/process.rs @@ -157,6 +157,7 @@ target_os = "xous", target_os = "trusty", target_os = "hermit", + target_os = "l4re", )) ))] mod tests; diff --git a/library/std/src/process/tests.rs b/library/std/src/process/tests.rs index 68c62a861075f..9fe14b2e468a5 100644 --- a/library/std/src/process/tests.rs +++ b/library/std/src/process/tests.rs @@ -28,7 +28,11 @@ fn shell_cmd() -> Command { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn smoke() { @@ -53,7 +57,11 @@ fn smoke_failure() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn exit_reported_right() { @@ -71,7 +79,11 @@ fn exit_reported_right() { #[test] #[cfg(unix)] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn signal_reported_right() { @@ -98,7 +110,11 @@ pub fn run_output(mut cmd: Command) -> String { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn stdout_works() { @@ -116,7 +132,11 @@ fn stdout_works() { #[test] #[cfg_attr(windows, ignore)] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn set_current_dir_works() { @@ -142,7 +162,11 @@ fn set_current_dir_works() { #[test] #[cfg_attr(windows, ignore)] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn stdin_works() { @@ -163,7 +187,11 @@ fn stdin_works() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn child_stdout_read_buf() { @@ -197,7 +225,11 @@ fn child_stdout_read_buf() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_process_status() { @@ -217,6 +249,7 @@ fn test_process_status() { } #[test] +#[cfg_attr(any(target_os = "l4re"), ignore = "no fork/exec available")] fn test_process_output_fail_to_start() { match Command::new("/no-binary-by-this-name-should-exist").output() { Err(e) => assert_eq!(e.kind(), ErrorKind::NotFound), @@ -226,7 +259,11 @@ fn test_process_output_fail_to_start() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_process_output_output() { @@ -244,7 +281,11 @@ fn test_process_output_output() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_process_output_error() { @@ -262,7 +303,11 @@ fn test_process_output_error() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_finish_once() { @@ -276,7 +321,11 @@ fn test_finish_once() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_finish_twice() { @@ -291,7 +340,11 @@ fn test_finish_twice() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_wait_with_output_once() { @@ -329,7 +382,11 @@ pub fn env_cmd() -> Command { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_override_env() { @@ -355,7 +412,11 @@ fn test_override_env() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_add_to_env() { @@ -370,7 +431,11 @@ fn test_add_to_env() { #[test] #[cfg_attr( - any(target_os = "vxworks", all(target_vendor = "apple", not(target_os = "macos"))), + any( + target_os = "vxworks", + all(target_vendor = "apple", not(target_os = "macos")), + target_os = "l4re" + ), ignore = "no shell available" )] fn test_capture_env_at_spawn() { @@ -654,6 +719,7 @@ fn run_canonical_bat_script() { } #[test] +#[cfg_attr(target_os = "l4re", ignore = "no shell available")] fn terminate_exited_process() { let mut cmd = if cfg!(target_os = "android") { let mut p = shell_cmd(); diff --git a/library/std/src/random.rs b/library/std/src/random.rs index ef561d1ed0c60..853756fcd32b6 100644 --- a/library/std/src/random.rs +++ b/library/std/src/random.rs @@ -103,7 +103,7 @@ use crate::sys::random as sys; /// Vita | `arc4random_buf` /// Hermit | `read_entropy` /// Horizon, Cygwin | `getrandom` -/// AIX, Hurd, L4Re, QNX | `/dev/urandom` +/// AIX, Hurd, QNX | `/dev/urandom` /// Redox | `/scheme/rand` /// RTEMS | [`arc4random_buf`](https://docs.rtems.org/branches/main/bsp-howto/getentropy.html) /// SGX | [`rdrand`](https://en.wikipedia.org/wiki/RDRAND) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 3caa41e16845d..d34621083406a 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -29,19 +29,20 @@ use libc::{ }; #[cfg(not(any( all(target_os = "linux", not(target_env = "musl")), - target_os = "l4re", target_os = "android", target_os = "hurd", + target_os = "l4re", )))] use libc::{ dirent as dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64, lstat as lstat64, off_t as off64_t, open as open64, stat as stat64, }; -#[cfg(any( - all(target_os = "linux", not(target_env = "musl")), - target_os = "l4re", - target_os = "hurd" -))] +#[cfg(target_os = "l4re")] +use libc::{ + dirent64, fstat as fstat64, ftruncate as ftruncate64, lseek as lseek64, lstat as lstat64, + off_t as off64_t, open as open64, stat as stat64, +}; +#[cfg(any(all(target_os = "linux", not(target_env = "musl")), target_os = "hurd"))] use libc::{dirent64, fstat64, ftruncate64, lseek64, lstat64, off64_t, open64, stat64}; use crate::ffi::{CStr, OsStr, OsString}; @@ -272,6 +273,7 @@ cfg_select! { target_os = "nto", target_os = "qnx", target_os = "vxworks", + target_os = "l4re", ) => { pub use crate::sys::fs::common::Dir; } @@ -560,7 +562,8 @@ impl FileAttr { target_os = "nto", target_os = "qnx", target_os = "aix", - target_os = "wasi" + target_os = "wasi", + target_os = "l4re" )))] impl FileAttr { #[cfg(not(any( @@ -686,7 +689,7 @@ impl FileAttr { } } -#[cfg(any(target_os = "nto", target_os = "qnx", target_os = "wasi"))] +#[cfg(any(target_os = "nto", target_os = "qnx", target_os = "wasi", target_os = "l4re"))] impl FileAttr { pub fn modified(&self) -> io::Result { SystemTime::new(self.stat.st_mtim.tv_sec, self.stat.st_mtim.tv_nsec.into()) @@ -1066,6 +1069,7 @@ impl DirEntry { target_os = "nto", target_os = "qnx", target_os = "vita", + target_os = "l4re", ))] pub fn file_type(&self) -> io::Result { self.metadata().map(|m| m.file_type()) @@ -1080,6 +1084,7 @@ impl DirEntry { target_os = "nto", target_os = "qnx", target_os = "vita", + target_os = "l4re", )))] pub fn file_type(&self) -> io::Result { match self.entry.d_type { @@ -1289,6 +1294,7 @@ impl File { target_os = "nto", target_os = "qnx", target_os = "hurd", + target_os = "l4re", ))] unsafe fn os_datasync(fd: c_int) -> c_int { libc::fdatasync(fd) @@ -1304,6 +1310,7 @@ impl File { target_os = "nto", target_os = "qnx", target_os = "hurd", + target_os = "l4re", target_vendor = "apple", )))] unsafe fn os_datasync(fd: c_int) -> c_int { @@ -1550,7 +1557,7 @@ impl File { pub fn set_times(&self, times: FileTimes) -> io::Result<()> { cfg_select! { - any(target_os = "redox", target_os = "espidf", target_os = "horizon", target_os = "nuttx") => { + any(target_os = "redox", target_os = "espidf", target_os = "horizon", target_os = "nuttx", target_os = "l4re") => { // Redox doesn't appear to support `UTIME_OMIT`. // ESP-IDF and HorizonOS do not support `futimens` at all and the behavior for those OS is therefore // the same as for Redox. @@ -1940,6 +1947,7 @@ pub fn link(original: &CStr, link: &CStr) -> io::Result<()> { // Other misc platforms target_os = "horizon", target_os = "vita", + target_os = "l4re", target_env = "nto70", ) => { cvt(unsafe { libc::link(original.as_ptr(), link.as_ptr()) })?; @@ -2308,6 +2316,7 @@ pub use remove_dir_impl::remove_dir_all; target_os = "nto", target_os = "qnx", target_os = "vxworks", + target_os = "l4re", miri ))] mod remove_dir_impl { @@ -2323,6 +2332,7 @@ mod remove_dir_impl { target_os = "nto", target_os = "qnx", target_os = "vxworks", + target_os = "l4re", miri )))] mod remove_dir_impl { diff --git a/library/std/src/sys/io/error/unix.rs b/library/std/src/sys/io/error/unix.rs index 89647ff27ca8e..5c51c5705a7aa 100644 --- a/library/std/src/sys/io/error/unix.rs +++ b/library/std/src/sys/io/error/unix.rs @@ -201,7 +201,8 @@ pub fn error_string(errno: i32) -> String { target_os = "linux", target_os = "hurd", target_env = "newlib", - target_os = "cygwin" + target_os = "cygwin", + target_env = "uclibc", ), not(target_env = "ohos") ), diff --git a/library/std/src/sys/net/connection/mod.rs b/library/std/src/sys/net/connection/mod.rs index 84b53fd375c93..49a0f47c959d2 100644 --- a/library/std/src/sys/net/connection/mod.rs +++ b/library/std/src/sys/net/connection/mod.rs @@ -1,6 +1,6 @@ cfg_select! { any( - all(target_family = "unix", not(target_os = "l4re")), + target_family = "unix", target_os = "windows", target_os = "hermit", all(target_os = "wasi", any(target_env = "p2", target_env = "p3")), diff --git a/library/std/src/sys/net/connection/socket/mod.rs b/library/std/src/sys/net/connection/socket/mod.rs index 66aa2a804db22..e3d06bbf65b2d 100644 --- a/library/std/src/sys/net/connection/socket/mod.rs +++ b/library/std/src/sys/net/connection/socket/mod.rs @@ -1,4 +1,5 @@ #[cfg(test)] +#[cfg(not(target_os = "l4re"))] mod tests; use crate::ffi::{c_int, c_void}; diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs index 8fca169d93119..f58c7f5cb95bc 100644 --- a/library/std/src/sys/pal/unix/mod.rs +++ b/library/std/src/sys/pal/unix/mod.rs @@ -145,6 +145,7 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { target_os = "horizon", target_os = "vxworks", target_os = "vita", + target_os = "l4re", // Unikraft's `signal` implementation is currently broken: // https://github.com/unikraft/lib-musl/issues/57 target_vendor = "unikraft", @@ -363,15 +364,24 @@ cfg_select! { _ => {} } -#[cfg(any(target_os = "espidf", target_os = "horizon", target_os = "vita", target_os = "nuttx"))] -pub mod unsupported { - use crate::io; - - pub fn unsupported() -> io::Result { - Err(unsupported_err()) - } +#[cfg(any( + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nuttx", + target_os = "l4re", +))] +pub fn unsupported() -> crate::io::Result { + Err(unsupported_err()) +} - pub fn unsupported_err() -> io::Error { - io::Error::UNSUPPORTED_PLATFORM - } +#[cfg(any( + target_os = "espidf", + target_os = "horizon", + target_os = "vita", + target_os = "nuttx", + target_os = "l4re", +))] +pub fn unsupported_err() -> crate::io::Error { + io::Error::UNSUPPORTED_PLATFORM } diff --git a/library/std/src/sys/personality/mod.rs b/library/std/src/sys/personality/mod.rs index 3b363aa2d024c..daa53703994b0 100644 --- a/library/std/src/sys/personality/mod.rs +++ b/library/std/src/sys/personality/mod.rs @@ -30,7 +30,7 @@ cfg_select! { target_os = "psp", target_os = "xous", target_os = "solid_asp3", - all(target_family = "unix", not(target_os = "espidf"), not(target_os = "l4re"), not(target_os = "nuttx")), + all(target_family = "unix", not(target_os = "espidf"), not(target_os = "nuttx")), all(target_vendor = "fortanix", target_env = "sgx"), ) => { mod gcc; diff --git a/library/std/src/sys/process/mod.rs b/library/std/src/sys/process/mod.rs index ee61175a278b0..f46870e0c4042 100644 --- a/library/std/src/sys/process/mod.rs +++ b/library/std/src/sys/process/mod.rs @@ -45,7 +45,8 @@ pub use imp::{ target_os = "espidf", target_os = "horizon", target_os = "vita", - target_os = "nuttx" + target_os = "nuttx", + target_os = "l4re" )) ), target_os = "windows", @@ -83,7 +84,8 @@ pub fn output(cmd: &mut Command) -> crate::io::Result<(ExitStatus, Vec, Vec< target_os = "espidf", target_os = "horizon", target_os = "vita", - target_os = "nuttx" + target_os = "nuttx", + target_os = "l4re" )) ), target_os = "windows", diff --git a/library/std/src/sys/process/unix/common.rs b/library/std/src/sys/process/unix/common.rs index 8215b196127ac..2e32770e90e77 100644 --- a/library/std/src/sys/process/unix/common.rs +++ b/library/std/src/sys/process/unix/common.rs @@ -12,7 +12,7 @@ use crate::path::Path; use crate::process::StdioPipes; use crate::sys::fd::FileDesc; use crate::sys::fs::File; -#[cfg(not(target_os = "fuchsia"))] +#[cfg(not(any(target_os = "fuchsia", target_os = "l4re")))] use crate::sys::fs::OpenOptions; use crate::sys::pipe::pipe; use crate::sys::process::env::{CommandEnv, CommandEnvs, CommandResolvedEnvs}; @@ -24,6 +24,9 @@ mod cstring_array; cfg_select! { target_os = "fuchsia" => { // fuchsia doesn't have /dev/null + }, + target_os = "l4re" => { + // l4re doesn't have /dev/null } target_os = "vxworks" => { const DEV_NULL: &CStr = c"/null"; @@ -119,9 +122,9 @@ pub enum ChildStdio { Explicit(c_int), Owned(FileDesc), - // On Fuchsia, null stdio is the default, so we simply don't specify - // any actions at the time of spawning. - #[cfg(target_os = "fuchsia")] + // On Fuchsia and L4Re, null stdio is the default, so we simply don't + // specify any actions at the time of spawning. + #[cfg(any(target_os = "fuchsia", target_os = "l4re"))] Null, } @@ -427,7 +430,7 @@ impl Stdio { Ok((ChildStdio::Owned(theirs), Some(ours))) } - #[cfg(not(target_os = "fuchsia"))] + #[cfg(not(any(target_os = "fuchsia", target_os = "l4re")))] Stdio::Null => { let mut opts = OpenOptions::new(); opts.read(readable); @@ -436,7 +439,7 @@ impl Stdio { Ok((ChildStdio::Owned(fd.into_inner()), None)) } - #[cfg(target_os = "fuchsia")] + #[cfg(any(target_os = "fuchsia", target_os = "l4re"))] Stdio::Null => Ok((ChildStdio::Null, None)), } } @@ -483,7 +486,7 @@ impl ChildStdio { ChildStdio::Explicit(fd) => Some(fd), ChildStdio::Owned(ref fd) => Some(fd.as_raw_fd()), - #[cfg(target_os = "fuchsia")] + #[cfg(any(target_os = "fuchsia", target_os = "l4re"))] ChildStdio::Null => None, } } diff --git a/library/std/src/sys/process/unix/common/tests.rs b/library/std/src/sys/process/unix/common/tests.rs index bc1d158b74861..eacb4d2d43122 100644 --- a/library/std/src/sys/process/unix/common/tests.rs +++ b/library/std/src/sys/process/unix/common/tests.rs @@ -19,6 +19,8 @@ macro_rules! t { // newly spawned process may just be raced in the macOS, so to prevent this // test from being flaky we ignore it on macOS. target_os = "macos", + // cat not available + target_os = "l4re", // When run under our current QEMU emulation test suite this test fails, // although the reason isn't very clear as to why. For now this test is // ignored there. @@ -84,6 +86,8 @@ fn test_process_mask() { any( // See test_process_mask target_os = "macos", + // cat not available + target_os = "l4re", target_arch = "arm", target_arch = "aarch64", target_arch = "riscv64", @@ -116,6 +120,8 @@ fn test_process_group_posix_spawn() { any( // See test_process_mask target_os = "macos", + // cat not available + target_os = "l4re", target_arch = "arm", target_arch = "aarch64", target_arch = "riscv64", @@ -154,6 +160,8 @@ fn test_process_group_no_posix_spawn() { any( // See test_process_mask target_os = "macos", + // cat not available + target_os = "l4re", target_arch = "arm", target_arch = "aarch64", target_arch = "riscv64", @@ -192,6 +200,8 @@ fn test_setsid_posix_spawn() { any( // See test_process_mask target_os = "macos", + // cat not available + target_os = "l4re", target_arch = "arm", target_arch = "aarch64", target_arch = "riscv64", diff --git a/library/std/src/sys/process/unix/mod.rs b/library/std/src/sys/process/unix/mod.rs index 837761431e990..47baf5a1e92ed 100644 --- a/library/std/src/sys/process/unix/mod.rs +++ b/library/std/src/sys/process/unix/mod.rs @@ -1,4 +1,7 @@ -#[cfg_attr(any(target_os = "espidf", target_os = "horizon", target_os = "nuttx"), allow(unused))] +#[cfg_attr( + any(target_os = "espidf", target_os = "horizon", target_os = "nuttx", target_os = "l4re"), + allow(unused) +)] mod common; cfg_select! { @@ -10,7 +13,7 @@ cfg_select! { mod vxworks; use vxworks as imp; } - any(target_os = "espidf", target_os = "horizon", target_os = "vita", target_os = "nuttx") => { + any(target_os = "espidf", target_os = "horizon", target_os = "vita", target_os = "nuttx", target_os = "l4re") => { mod unsupported; use unsupported as imp; pub use unsupported::output; diff --git a/library/std/src/sys/process/unix/unix/tests.rs b/library/std/src/sys/process/unix/unix/tests.rs index 663ba61f966c9..9a029f16a3a20 100644 --- a/library/std/src/sys/process/unix/unix/tests.rs +++ b/library/std/src/sys/process/unix/unix/tests.rs @@ -51,7 +51,10 @@ fn exitstatus_display_tests() { #[test] #[cfg_attr(target_os = "emscripten", ignore)] -#[cfg_attr(any(target_os = "tvos", target_os = "watchos"), ignore = "fork is prohibited")] +#[cfg_attr( + any(target_os = "tvos", target_os = "watchos", target_os = "l4re"), + ignore = "fork is prohibited" +)] fn test_command_fork_no_unwind() { let got = catch_unwind(|| { let mut c = Command::new("echo"); diff --git a/library/std/src/sys/process/unix/unsupported.rs b/library/std/src/sys/process/unix/unsupported.rs index 17421d1e2e35d..2235ec1f1c3b6 100644 --- a/library/std/src/sys/process/unix/unsupported.rs +++ b/library/std/src/sys/process/unix/unsupported.rs @@ -4,7 +4,7 @@ use super::common::*; use crate::io; use crate::num::NonZero; use crate::process::StdioPipes; -use crate::sys::pal::unsupported::*; +use crate::sys::pal::{unsupported, unsupported_err}; //////////////////////////////////////////////////////////////////////////////// // Command diff --git a/library/std/src/sys/random/mod.rs b/library/std/src/sys/random/mod.rs index e5a66dc463c6b..5b0d19cc63eca 100644 --- a/library/std/src/sys/random/mod.rs +++ b/library/std/src/sys/random/mod.rs @@ -52,7 +52,6 @@ cfg_select! { any( target_os = "aix", target_os = "hurd", - target_os = "l4re", target_os = "nto", target_os = "qnx", ) => { @@ -107,6 +106,7 @@ cfg_select! { all(target_family = "wasm", target_os = "unknown"), target_os = "xous", target_os = "vexos", + target_os = "l4re", ) => { // FIXME: finally remove std support for wasm32-unknown-unknown // FIXME: add random data generation to xous @@ -123,6 +123,7 @@ cfg_select! { all(target_os = "wasi", not(target_env = "p1")), target_os = "xous", target_os = "vexos", + target_os = "l4re", )))] pub fn hashmap_random_keys() -> (u64, u64) { let mut buf = [0; 16]; diff --git a/library/std/src/thread/functions.rs b/library/std/src/thread/functions.rs index 21e7a2b2ed087..355a00c2a95ad 100644 --- a/library/std/src/thread/functions.rs +++ b/library/std/src/thread/functions.rs @@ -681,13 +681,10 @@ pub fn park_timeout(dur: Duration) { /// # Examples /// /// ``` -/// # #![allow(dead_code)] -/// use std::{io, thread}; +/// use std::thread; /// -/// fn main() -> io::Result<()> { -/// let count = thread::available_parallelism()?.get(); -/// assert!(count >= 1_usize); -/// Ok(()) +/// if let Ok(count) = thread::available_parallelism() { +/// assert!(count.get() >= 1_usize); /// } /// ``` #[doc(alias = "available_concurrency")] // Alias for a previous name we gave this API on unstable. diff --git a/library/std/tests/env.rs b/library/std/tests/env.rs index 9d624d5592ce7..758d0a069a831 100644 --- a/library/std/tests/env.rs +++ b/library/std/tests/env.rs @@ -4,7 +4,10 @@ use std::path::Path; mod common; #[test] -#[cfg_attr(any(target_os = "emscripten", target_os = "wasi", target_env = "sgx"), ignore)] +#[cfg_attr( + any(target_os = "emscripten", target_os = "wasi", target_env = "sgx", target_os = "l4re"), + ignore +)] fn test_self_exe_path() { let path = current_exe(); assert!(path.is_ok()); diff --git a/library/std/tests/pipe_subprocess.rs b/library/std/tests/pipe_subprocess.rs index 9643c3b7bdad8..c14db690224db 100644 --- a/library/std/tests/pipe_subprocess.rs +++ b/library/std/tests/pipe_subprocess.rs @@ -1,6 +1,10 @@ fn main() { - // No `Command` on Miri and emscripten - #[cfg(all(not(miri), any(unix, windows), not(target_os = "emscripten")))] + // No `Command` on Miri, emscripten or L4Re + #[cfg(all( + not(miri), + any(unix, windows), + not(any(target_os = "emscripten", target_os = "l4re")) + ))] { use std::io::{Read, pipe}; use std::{env, process}; diff --git a/library/std/tests/process_spawning.rs b/library/std/tests/process_spawning.rs index 80e712a2388a1..b7a9a1077696b 100644 --- a/library/std/tests/process_spawning.rs +++ b/library/std/tests/process_spawning.rs @@ -7,7 +7,10 @@ mod common; #[test] // Process spawning not supported by Miri, Emscripten and wasi #[cfg_attr(any(miri, target_os = "emscripten", target_os = "wasi"), ignore)] -#[cfg_attr(any(target_os = "tvos", target_os = "watchos"), ignore = "fork is prohibited")] +#[cfg_attr( + any(target_os = "tvos", target_os = "watchos", target_os = "l4re"), + ignore = "fork is prohibited" +)] fn issue_15149() { // If we're the parent, copy our own binary to a new directory. let my_path = env::current_exe().unwrap(); diff --git a/library/std/tests/time.rs b/library/std/tests/time.rs index d6736e25ace18..6d8b4cbfd094f 100644 --- a/library/std/tests/time.rs +++ b/library/std/tests/time.rs @@ -181,6 +181,7 @@ fn system_time_elapsed() { } #[test] +#[cfg_attr(target_os = "l4re", ignore = "No wallclock time support in L4Re")] fn since_epoch() { let ts = SystemTime::now(); let a = ts.duration_since(UNIX_EPOCH + Duration::SECOND).unwrap(); diff --git a/library/unwind/src/lib.rs b/library/unwind/src/lib.rs index 3725375a713dc..eba08aec4d109 100644 --- a/library/unwind/src/lib.rs +++ b/library/unwind/src/lib.rs @@ -19,7 +19,6 @@ cfg_select! { // Windows MSVC no extra unwinder support needed } any( - target_os = "l4re", target_os = "none", target_os = "espidf", target_os = "nuttx", @@ -31,6 +30,7 @@ cfg_select! { windows, target_os = "psp", target_os = "solid_asp3", + target_os = "l4re", all(target_vendor = "fortanix", target_env = "sgx"), all(target_os = "wasi", panic = "unwind"), target_os = "xous", diff --git a/src/bootstrap/src/utils/helpers.rs b/src/bootstrap/src/utils/helpers.rs index f4a9b5704a434..8cddd822c806e 100644 --- a/src/bootstrap/src/utils/helpers.rs +++ b/src/bootstrap/src/utils/helpers.rs @@ -227,7 +227,8 @@ pub fn use_host_linker(target: TargetSelection) -> bool { || target.contains("fortanix") || target.contains("fuchsia") || target.contains("bpf") - || target.contains("switch")) + || target.contains("switch") + || target.contains("l4re")) } pub fn target_supports_cranelift_backend(target: TargetSelection) -> bool { From f639348a1b537c5f697161d839b2ce64ce6767be Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Thu, 6 Aug 2026 14:12:38 +0300 Subject: [PATCH 068/100] expand: Change feature gate wording for `feature(proc_macro_hygiene)` --- compiler/rustc_expand/src/expand.rs | 2 +- tests/ui/eii/errors.rs | 2 +- tests/ui/eii/errors.stderr | 2 +- tests/ui/macros/issue-111749.rs | 2 +- tests/ui/macros/issue-111749.stderr | 2 +- tests/ui/proc-macro/cfg-eval-fail.rs | 2 +- tests/ui/proc-macro/cfg-eval-fail.stderr | 2 +- tests/ui/proc-macro/proc-macro-gates.rs | 12 ++++++------ tests/ui/proc-macro/proc-macro-gates.stderr | 12 ++++++------ 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 4846b48af8d5e..c80ffe625f248 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -1046,7 +1046,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> { self.cx.sess, sym::proc_macro_hygiene, span, - format!("custom attributes cannot be applied to {kind}"), + format!("macro attributes on {kind} are unstable"), ) .emit(); } diff --git a/tests/ui/eii/errors.rs b/tests/ui/eii/errors.rs index 3b28e268662ef..b3bb4cc031bd8 100644 --- a/tests/ui/eii/errors.rs +++ b/tests/ui/eii/errors.rs @@ -8,7 +8,7 @@ #[eii_declaration(bar)] //~ ERROR `#[eii_declaration(...)]` is only valid on macros fn hello() { #[eii_declaration(bar)] //~ ERROR `#[eii_declaration(...)]` is only valid on macros - let x = 3 + 3; //~| ERROR custom attributes cannot be applied to statements + let x = 3 + 3; //~| ERROR macro attributes on statements are unstable } #[eii_declaration] //~ ERROR `#[eii_declaration(...)]` expects a list of one or two elements diff --git a/tests/ui/eii/errors.stderr b/tests/ui/eii/errors.stderr index 512cd135de4c3..28411cfd5108a 100644 --- a/tests/ui/eii/errors.stderr +++ b/tests/ui/eii/errors.stderr @@ -4,7 +4,7 @@ error: `#[eii_declaration(...)]` is only valid on macros LL | #[eii_declaration(bar)] | ^^^^^^^^^^^^^^^^^^^^^^^ -error[E0658]: custom attributes cannot be applied to statements +error[E0658]: macro attributes on statements are unstable --> $DIR/errors.rs:10:5 | LL | #[eii_declaration(bar)] diff --git a/tests/ui/macros/issue-111749.rs b/tests/ui/macros/issue-111749.rs index 799fee22685ab..7c10038925111 100644 --- a/tests/ui/macros/issue-111749.rs +++ b/tests/ui/macros/issue-111749.rs @@ -9,5 +9,5 @@ fn main() { //~^ ERROR the `test` attribute may only be used on a free function //~| ERROR attribute must be of the form `#[test]` //~| WARNING this was previously accepted by the compiler but is being phased out - //~| ERROR custom attributes cannot be applied to expressions + //~| ERROR macro attributes on expressions are unstable } diff --git a/tests/ui/macros/issue-111749.stderr b/tests/ui/macros/issue-111749.stderr index f2773e7029ab5..3207aa182abec 100644 --- a/tests/ui/macros/issue-111749.stderr +++ b/tests/ui/macros/issue-111749.stderr @@ -1,4 +1,4 @@ -error[E0658]: custom attributes cannot be applied to expressions +error[E0658]: macro attributes on expressions are unstable --> $DIR/issue-111749.rs:8:17 | LL | cbor_map! { #[test(test)] 4i32}; diff --git a/tests/ui/proc-macro/cfg-eval-fail.rs b/tests/ui/proc-macro/cfg-eval-fail.rs index 2cde895f2ea44..d9256cfa3377d 100644 --- a/tests/ui/proc-macro/cfg-eval-fail.rs +++ b/tests/ui/proc-macro/cfg-eval-fail.rs @@ -4,5 +4,5 @@ fn main() { let _ = #[cfg_eval] #[cfg(false)] 0; //~^ ERROR removing an expression is not supported in this position - //~| ERROR custom attributes cannot be applied to expressions + //~| ERROR macro attributes on expressions are unstable } diff --git a/tests/ui/proc-macro/cfg-eval-fail.stderr b/tests/ui/proc-macro/cfg-eval-fail.stderr index 61da346fa69f6..6cd3f54d6fafd 100644 --- a/tests/ui/proc-macro/cfg-eval-fail.stderr +++ b/tests/ui/proc-macro/cfg-eval-fail.stderr @@ -4,7 +4,7 @@ error: removing an expression is not supported in this position LL | let _ = #[cfg_eval] #[cfg(false)] 0; | ^^^^^^^^^^^^^ -error[E0658]: custom attributes cannot be applied to expressions +error[E0658]: macro attributes on expressions are unstable --> $DIR/cfg-eval-fail.rs:5:13 | LL | let _ = #[cfg_eval] #[cfg(false)] 0; diff --git a/tests/ui/proc-macro/proc-macro-gates.rs b/tests/ui/proc-macro/proc-macro-gates.rs index 04e097eb2f745..a201836851761 100644 --- a/tests/ui/proc-macro/proc-macro-gates.rs +++ b/tests/ui/proc-macro/proc-macro-gates.rs @@ -23,26 +23,26 @@ fn attrs() { struct S; // Statement, macro - #[empty_attr] //~ ERROR: custom attributes cannot be applied to statements + #[empty_attr] //~ ERROR: macro attributes on statements are unstable println!(); // Statement, semi - #[empty_attr] //~ ERROR: custom attributes cannot be applied to statements + #[empty_attr] //~ ERROR: macro attributes on statements are unstable S; // Statement, local - #[empty_attr] //~ ERROR: custom attributes cannot be applied to statements + #[empty_attr] //~ ERROR: macro attributes on statements are unstable let _x = 2; // Expr - let _x = #[identity_attr] 2; //~ ERROR: custom attributes cannot be applied to expressions + let _x = #[identity_attr] 2; //~ ERROR: macro attributes on expressions are unstable // Opt expr - let _x = [#[identity_attr] 2]; //~ ERROR: custom attributes cannot be applied to expressions + let _x = [#[identity_attr] 2]; //~ ERROR: macro attributes on expressions are unstable // Expr macro let _x = #[identity_attr] println!(); - //~^ ERROR: custom attributes cannot be applied to expressions + //~^ ERROR: macro attributes on expressions are unstable } fn test_case() { diff --git a/tests/ui/proc-macro/proc-macro-gates.stderr b/tests/ui/proc-macro/proc-macro-gates.stderr index 3607b062a5fcb..9a243f4f900b0 100644 --- a/tests/ui/proc-macro/proc-macro-gates.stderr +++ b/tests/ui/proc-macro/proc-macro-gates.stderr @@ -24,7 +24,7 @@ error: key-value macro attributes are not supported LL | #[empty_attr = "y"] | ^^^^^^^^^^^^^^^^^^^ -error[E0658]: custom attributes cannot be applied to statements +error[E0658]: macro attributes on statements are unstable --> $DIR/proc-macro-gates.rs:26:5 | LL | #[empty_attr] @@ -34,7 +34,7 @@ LL | #[empty_attr] = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0658]: custom attributes cannot be applied to statements +error[E0658]: macro attributes on statements are unstable --> $DIR/proc-macro-gates.rs:30:5 | LL | #[empty_attr] @@ -44,7 +44,7 @@ LL | #[empty_attr] = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0658]: custom attributes cannot be applied to statements +error[E0658]: macro attributes on statements are unstable --> $DIR/proc-macro-gates.rs:34:5 | LL | #[empty_attr] @@ -54,7 +54,7 @@ LL | #[empty_attr] = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0658]: custom attributes cannot be applied to expressions +error[E0658]: macro attributes on expressions are unstable --> $DIR/proc-macro-gates.rs:38:14 | LL | let _x = #[identity_attr] 2; @@ -64,7 +64,7 @@ LL | let _x = #[identity_attr] 2; = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0658]: custom attributes cannot be applied to expressions +error[E0658]: macro attributes on expressions are unstable --> $DIR/proc-macro-gates.rs:41:15 | LL | let _x = [#[identity_attr] 2]; @@ -74,7 +74,7 @@ LL | let _x = [#[identity_attr] 2]; = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0658]: custom attributes cannot be applied to expressions +error[E0658]: macro attributes on expressions are unstable --> $DIR/proc-macro-gates.rs:44:14 | LL | let _x = #[identity_attr] println!(); From c1af1957b58902bb4b2d7e879c1dc27693ada73d Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 4 Aug 2026 11:48:44 +0200 Subject: [PATCH 069/100] cleanup `DefiningTy::new` The field of `BodyOwnerKind` is computed via the exact same way as this check. --- .../rustc_borrowck/src/universal_regions.rs | 52 ++++++++----------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_borrowck/src/universal_regions.rs b/compiler/rustc_borrowck/src/universal_regions.rs index 694f29b942e4f..4540193f60baa 100644 --- a/compiler/rustc_borrowck/src/universal_regions.rs +++ b/compiler/rustc_borrowck/src/universal_regions.rs @@ -155,36 +155,28 @@ impl<'tcx> DefiningTy<'tcx> { } } - BodyOwnerKind::Const { .. } | BodyOwnerKind::Static(..) => { - match tcx.def_kind(body_def_id) { - DefKind::AnonConst - if tcx.anon_const_kind(body_def_id) - == ty::AnonConstKind::NonTypeSystemInline => - { - // This is required for `AscribeUserType` canonical query, which will call - // `type_of(inline_const_def_id)`. That `type_of` would inject erased lifetimes - // into borrowck, which is ICE #78174. - // - // As a workaround, inline consts have an additional generic param (`ty` - // below), so that `type_of(inline_const_def_id).substs(substs)` uses the - // proper type with NLL infer vars. - // - // Fetch the actual type from MIR, as `type_of` returns something useless - // like ``. - let body = tcx.mir_promoted(body_def_id).0.borrow(); - let ty = body.local_decls[RETURN_PLACE].ty; - let typeck_root_def_id = tcx.typeck_root_def_id(body_def_id.to_def_id()); - let parent_args = GenericArgs::identity_for_item(tcx, typeck_root_def_id); - let args = - InlineConstArgs::new(tcx, InlineConstArgsParts { parent_args, ty }) - .args; - DefiningTy::InlineConst(body_def_id.to_def_id(), args) - } - _ => { - let args = GenericArgs::identity_for_item(tcx, body_def_id.to_def_id()); - DefiningTy::Const(body_def_id.to_def_id(), args) - } - } + BodyOwnerKind::Const { inline: true } => { + // This is required for `AscribeUserType` canonical query, which will call + // `type_of(inline_const_def_id)`. That `type_of` would inject erased lifetimes + // into borrowck, which is ICE #78174. + // + // As a workaround, inline consts have an additional generic param (`ty` + // below), so that `type_of(inline_const_def_id).substs(substs)` uses the + // proper type with NLL infer vars. + // + // Fetch the actual type from MIR, as `type_of` returns something useless + // like ``. + let body = tcx.mir_promoted(body_def_id).0.borrow(); + let ty = body.local_decls[RETURN_PLACE].ty; + let typeck_root_def_id = tcx.typeck_root_def_id(body_def_id.to_def_id()); + let parent_args = GenericArgs::identity_for_item(tcx, typeck_root_def_id); + let args = InlineConstArgs::new(tcx, InlineConstArgsParts { parent_args, ty }).args; + DefiningTy::InlineConst(body_def_id.to_def_id(), args) + } + + BodyOwnerKind::Const { inline: false } | BodyOwnerKind::Static(..) => { + let args = GenericArgs::identity_for_item(tcx, body_def_id.to_def_id()); + DefiningTy::Const(body_def_id.to_def_id(), args) } BodyOwnerKind::GlobalAsm => DefiningTy::GlobalAsm(body_def_id.to_def_id()), From b326732355c6d6804ea456c7a64026703c021f92 Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 4 Aug 2026 11:09:44 +0200 Subject: [PATCH 070/100] make the c_variadic region late bound --- .../rustc_borrowck/src/universal_regions.rs | 95 +++++++++++++------ tests/ui/c-variadic/not-async.stderr | 18 ++-- tests/ui/c-variadic/variadic-ffi-4.stderr | 8 +- .../note-and-explain-ReVar-124973.stderr | 9 +- 4 files changed, 80 insertions(+), 50 deletions(-) diff --git a/compiler/rustc_borrowck/src/universal_regions.rs b/compiler/rustc_borrowck/src/universal_regions.rs index 4540193f60baa..3479224cc5546 100644 --- a/compiler/rustc_borrowck/src/universal_regions.rs +++ b/compiler/rustc_borrowck/src/universal_regions.rs @@ -26,8 +26,8 @@ use rustc_macros::extension; use rustc_middle::mir::RETURN_PLACE; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{ - self, GenericArgs, GenericArgsRef, InlineConstArgs, InlineConstArgsParts, RegionExt, RegionVid, - Ty, TyCtxt, TypeFoldable, TypeVisitableExt, fold_regions, + self, BoundVariableKind, GenericArgs, GenericArgsRef, InlineConstArgs, InlineConstArgsParts, + List, RegionExt, RegionVid, Ty, TyCtxt, TypeFoldable, TypeVisitableExt, fold_regions, }; use rustc_middle::{bug, span_bug}; use rustc_span::{ErrorGuaranteed, kw, sym}; @@ -183,21 +183,52 @@ impl<'tcx> DefiningTy<'tcx> { } } - #[instrument(level = "debug", skip(tcx, c_variadic_region), ret)] - fn inputs_and_output( - self, - tcx: TyCtxt<'tcx>, - c_variadic_region: impl FnOnce() -> ty::Region<'tcx>, - ) -> ty::Binder<'tcx, &'tcx ty::List>> { + /// The bound variables for a given defining type. This differs from their usual bound vars + /// in that closures and coroutine closures have an additional `'env`, while C-variadic + /// functions have an additional region for their implicit `VaList` input. + pub(crate) fn bound_vars(self, tcx: TyCtxt<'tcx>) -> &'tcx List> { + match self { + DefiningTy::Closure(_, args) => { + let closure_sig = args.as_closure().sig(); + let inputs_and_output = closure_sig.inputs_and_output(); + tcx.mk_bound_variable_kinds_from_iter(inputs_and_output.bound_vars().iter().chain( + iter::once(ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv)), + )) + } + + DefiningTy::CoroutineClosure(_, args) => { + let closure_sig = args.as_coroutine_closure().coroutine_closure_sig(); + tcx.mk_bound_variable_kinds_from_iter(closure_sig.bound_vars().iter().chain( + iter::once(ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv)), + )) + } + + DefiningTy::FnDef(def_id, _) => { + let sig = tcx.fn_sig(def_id).instantiate_identity().skip_norm_wip(); + if sig.skip_binder().c_variadic() { + // FIXME(#160495): Don't use an anonymous region here + tcx.mk_bound_variable_kinds_from_iter(sig.bound_vars().iter().chain( + iter::once(ty::BoundVariableKind::Region(ty::BoundRegionKind::Anon)), + )) + } else { + sig.bound_vars() + } + } + + DefiningTy::Coroutine(..) + | DefiningTy::Const(..) + | DefiningTy::InlineConst(..) + | DefiningTy::GlobalAsm(..) => ty::List::empty(), + } + } + + #[instrument(level = "debug", skip(tcx), ret)] + fn inputs_and_output(self, tcx: TyCtxt<'tcx>) -> ty::Binder<'tcx, &'tcx ty::List>> { match self { DefiningTy::Closure(def_id, args) => { let closure_sig = args.as_closure().sig(); let inputs_and_output = closure_sig.inputs_and_output(); - let bound_vars = tcx.mk_bound_variable_kinds_from_iter( - inputs_and_output.bound_vars().iter().chain(iter::once( - ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv), - )), - ); + let bound_vars = self.bound_vars(tcx); let br = ty::BoundRegion { var: ty::BoundVar::from_usize(bound_vars.len() - 1), kind: ty::BoundRegionKind::ClosureEnv, @@ -245,10 +276,7 @@ impl<'tcx> DefiningTy<'tcx> { // Then we wrap it all up into a list of inputs and output. DefiningTy::CoroutineClosure(def_id, args) => { let closure_sig = args.as_coroutine_closure().coroutine_closure_sig(); - let bound_vars = - tcx.mk_bound_variable_kinds_from_iter(closure_sig.bound_vars().iter().chain( - iter::once(ty::BoundVariableKind::Region(ty::BoundRegionKind::ClosureEnv)), - )); + let bound_vars = self.bound_vars(tcx); let br = ty::BoundRegion { var: ty::BoundVar::from_usize(bound_vars.len() - 1), kind: ty::BoundRegionKind::ClosureEnv, @@ -290,17 +318,24 @@ impl<'tcx> DefiningTy<'tcx> { if tcx.fn_sig(def_id).skip_binder().c_variadic() { let va_list_did = tcx.require_lang_item(LangItem::VaList, tcx.def_span(def_id)); - let region = c_variadic_region(); + let bound_vars = self.bound_vars(tcx); + let br = ty::BoundRegion { + var: ty::BoundVar::from_usize(bound_vars.len() - 1), + kind: ty::BoundRegionKind::Anon, + }; + let region = ty::Region::new_bound(tcx, ty::INNERMOST, br); let va_list_ty = tcx.type_of(va_list_did).instantiate(tcx, &[region.into()]).skip_norm_wip(); // The signature needs to follow the order [input_tys, va_list_ty, output_ty] - return inputs_and_output.map_bound(|tys| { - let (output_ty, input_tys) = tys.split_last().unwrap(); + let (output_ty, input_tys) = + inputs_and_output.skip_binder().split_last().unwrap(); + return ty::Binder::bind_with_vars( tcx.mk_type_list_from_iter( input_tys.iter().copied().chain([va_list_ty, *output_ty]), - ) - }); + ), + bound_vars, + ); } inputs_and_output @@ -678,7 +713,9 @@ impl<'tcx> UniversalRegionsBuilder<'_, 'tcx> { } else { // If this is a closure, coroutine, or inline-const, then the late-bound regions from the enclosing // function/closures are actually external regions to us. For example, here, 'a is not local - // to the closure c (although it is local to the fn foo): + // to the closure c (although it is local to the fn foo). We need to add them as they could be + // explicitly named in this body: + // // fn foo<'a>() { // let c = || { let x: &'a u32 = ...; } // } @@ -708,8 +745,9 @@ impl<'tcx> UniversalRegionsBuilder<'_, 'tcx> { // on its signature are local. // // We manually loop over `bound_inputs_and_output` instead of using - // `for_each_late_bound_region_in_item` as we may need to add the otherwise - // implicit `ClosureEnv` region. + // `for_each_late_bound_region_in_item` as both closures and function + // definitions have implicit late bound regions. Closures have a `'env` + // regions while c-variadic function definitions have a `&VaList` argument. let bound_inputs_and_output = self.compute_inputs_and_output(&indices, defining_ty); for (idx, bound_var) in bound_inputs_and_output.bound_vars().iter().enumerate() { if let ty::BoundVariableKind::Region(kind) = bound_var { @@ -825,12 +863,7 @@ impl<'tcx> UniversalRegionsBuilder<'_, 'tcx> { defining_ty: DefiningTy<'tcx>, ) -> ty::Binder<'tcx, &'tcx ty::List>> { let tcx = self.infcx.tcx; - let inputs_and_output = defining_ty.inputs_and_output(tcx, || { - self.infcx.next_nll_region_var(NllRegionVariableOrigin::FreeRegion, || { - RegionCtxt::Free(sym::c_dash_variadic) - }) - }); - + let inputs_and_output = defining_ty.inputs_and_output(tcx); let inputs_and_output = indices.fold_to_region_vids(tcx, inputs_and_output); // FIXME(#129952): We probably want a more principled approach here. diff --git a/tests/ui/c-variadic/not-async.stderr b/tests/ui/c-variadic/not-async.stderr index 921210382236c..9a81e0ce270d6 100644 --- a/tests/ui/c-variadic/not-async.stderr +++ b/tests/ui/c-variadic/not-async.stderr @@ -14,21 +14,19 @@ error[E0700]: hidden type for `impl Future` captures lifetime that --> $DIR/not-async.rs:4:65 | LL | async unsafe extern "C" fn fn_cannot_be_async(x: isize, _: ...) {} - | -^^ - | | - | opaque type defined here - | - = note: hidden type `{async fn body of fn_cannot_be_async()}` captures lifetime `'_` + | ----------------------------------------------------------------^^ + | | | + | | opaque type defined here + | hidden type `{async fn body of fn_cannot_be_async()}` captures the anonymous lifetime as defined here error[E0700]: hidden type for `impl Future` captures lifetime that does not appear in bounds --> $DIR/not-async.rs:11:73 | LL | async unsafe extern "C" fn method_cannot_be_async(x: isize, _: ...) {} - | -^^ - | | - | opaque type defined here - | - = note: hidden type `{async fn body of S::method_cannot_be_async()}` captures lifetime `'_` + | --------------------------------------------------------------------^^ + | | | + | | opaque type defined here + | hidden type `{async fn body of S::method_cannot_be_async()}` captures the anonymous lifetime as defined here error: aborting due to 4 previous errors diff --git a/tests/ui/c-variadic/variadic-ffi-4.stderr b/tests/ui/c-variadic/variadic-ffi-4.stderr index d53f1f527748c..a92a5fd4bf61d 100644 --- a/tests/ui/c-variadic/variadic-ffi-4.stderr +++ b/tests/ui/c-variadic/variadic-ffi-4.stderr @@ -30,9 +30,9 @@ error: lifetime may not live long enough --> $DIR/variadic-ffi-4.rs:21:5 | LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) { - | ------- ------- has type `VaList<'1>` + | ------- ------- has type `VaList<'2>` | | - | has type `&mut VaList<'2>` + | has type `&mut VaList<'1>` LL | ap0 = &mut ap1; | ^^^^^^^^^^^^^^ assignment requires that `'1` must outlive `'2` | @@ -44,9 +44,9 @@ error: lifetime may not live long enough --> $DIR/variadic-ffi-4.rs:21:5 | LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) { - | ------- ------- has type `VaList<'1>` + | ------- ------- has type `VaList<'2>` | | - | has type `&mut VaList<'2>` + | has type `&mut VaList<'1>` LL | ap0 = &mut ap1; | ^^^^^^^^^^^^^^ assignment requires that `'2` must outlive `'1` | diff --git a/tests/ui/inference/note-and-explain-ReVar-124973.stderr b/tests/ui/inference/note-and-explain-ReVar-124973.stderr index 3610fa82754b9..3ba76eb2ece18 100644 --- a/tests/ui/inference/note-and-explain-ReVar-124973.stderr +++ b/tests/ui/inference/note-and-explain-ReVar-124973.stderr @@ -8,11 +8,10 @@ error[E0700]: hidden type for `impl Future` captures lifetime that --> $DIR/note-and-explain-ReVar-124973.rs:3:76 | LL | async unsafe extern "C" fn multiple_named_lifetimes<'a, 'b>(_: u8, _: ...) {} - | -^^ - | | - | opaque type defined here - | - = note: hidden type `{async fn body of multiple_named_lifetimes<'a, 'b>()}` captures lifetime `'_` + | ---------------------------------------------------------------------------^^ + | | | + | | opaque type defined here + | hidden type `{async fn body of multiple_named_lifetimes<'a, 'b>()}` captures the anonymous lifetime as defined here error: aborting due to 2 previous errors From 234a308c1d683770dca4db2f4f613c713dbf8aec Mon Sep 17 00:00:00 2001 From: aerooneqq Date: Thu, 6 Aug 2026 15:20:35 +0300 Subject: [PATCH 071/100] Fix determining wrong fn kind when delegation is inside const arg --- compiler/rustc_hir_analysis/src/delegation.rs | 21 ++++++++-------- .../ui/delegation/wrong-fn-kind-ice-159127.rs | 15 ++++++++++++ .../wrong-fn-kind-ice-159127.stderr | 24 +++++++++++++++++++ 3 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 tests/ui/delegation/wrong-fn-kind-ice-159127.rs create mode 100644 tests/ui/delegation/wrong-fn-kind-ice-159127.stderr diff --git a/compiler/rustc_hir_analysis/src/delegation.rs b/compiler/rustc_hir_analysis/src/delegation.rs index d33a1cf736738..ab34246d9716e 100644 --- a/compiler/rustc_hir_analysis/src/delegation.rs +++ b/compiler/rustc_hir_analysis/src/delegation.rs @@ -2,8 +2,6 @@ //! //! For more information about delegation design, see the tracking issue #118212. -use std::debug_assert_matches; - use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; @@ -104,14 +102,17 @@ enum FnKind { fn fn_kind<'tcx>(tcx: TyCtxt<'tcx>, def_id: impl Into) -> FnKind { let def_id = def_id.into(); - debug_assert_matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn); - - let parent = tcx.parent(def_id); - match tcx.def_kind(parent) { - DefKind::Trait => FnKind::AssocTrait, - DefKind::Impl { of_trait: true } => FnKind::AssocTraitImpl, - DefKind::Impl { of_trait: false } => FnKind::AssocInherentImpl, - _ => FnKind::Free, + match tcx.def_kind(def_id) { + DefKind::Fn => FnKind::Free, + DefKind::AssocFn => match tcx.def_kind(tcx.parent(def_id)) { + DefKind::Trait => FnKind::AssocTrait, + DefKind::Impl { of_trait } => match of_trait { + true => FnKind::AssocTraitImpl, + false => FnKind::AssocInherentImpl, + }, + _ => unreachable!("associated function can only be in trait or impl"), + }, + _ => unreachable!("delegation/signature can be either free or associated function"), } } diff --git a/tests/ui/delegation/wrong-fn-kind-ice-159127.rs b/tests/ui/delegation/wrong-fn-kind-ice-159127.rs new file mode 100644 index 0000000000000..e117826ef099c --- /dev/null +++ b/tests/ui/delegation/wrong-fn-kind-ice-159127.rs @@ -0,0 +1,15 @@ +#![feature(fn_delegation)] +#![feature(min_generic_const_args)] + +impl + core::direct_const_arg!({ + //~^ ERROR: expected type, found `direct_const_arg!()` constant + fn foo() {} + reuse foo::<>as bar; + reuse bar; + //~^ ERROR: the name `bar` is defined multiple times + }) +{ +} + +fn main() {} diff --git a/tests/ui/delegation/wrong-fn-kind-ice-159127.stderr b/tests/ui/delegation/wrong-fn-kind-ice-159127.stderr new file mode 100644 index 0000000000000..bc218f39ab65a --- /dev/null +++ b/tests/ui/delegation/wrong-fn-kind-ice-159127.stderr @@ -0,0 +1,24 @@ +error[E0428]: the name `bar` is defined multiple times + --> $DIR/wrong-fn-kind-ice-159127.rs:9:9 + | +LL | reuse foo::<>as bar; + | -------------------- previous definition of the value `bar` here +LL | reuse bar; + | ^^^^^^^^^^ `bar` redefined here + | + = note: `bar` must be defined only once in the value namespace of this block + +error: expected type, found `direct_const_arg!()` constant + --> $DIR/wrong-fn-kind-ice-159127.rs:5:5 + | +LL | / core::direct_const_arg!({ +LL | | +LL | | fn foo() {} +LL | | reuse foo::<>as bar; +... | +LL | | }) + | |______^ + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0428`. From 8bb9f279a9c384960b8c13b5c70d97d1b4d64067 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 5 Aug 2026 00:24:45 +0200 Subject: [PATCH 072/100] refactor handling of target features in Session --- compiler/rustc_ast_lowering/src/asm.rs | 2 +- .../rustc_codegen_cranelift/src/inline_asm.rs | 2 +- compiler/rustc_codegen_cranelift/src/lib.rs | 6 +- compiler/rustc_codegen_gcc/src/lib.rs | 7 +- compiler/rustc_codegen_llvm/src/asm.rs | 4 +- compiler/rustc_codegen_llvm/src/attributes.rs | 4 +- compiler/rustc_codegen_llvm/src/back/write.rs | 2 +- compiler/rustc_codegen_llvm/src/llvm_util.rs | 18 +- .../src/back/link/raw_dylib.rs | 2 +- .../rustc_codegen_ssa/src/back/metadata.rs | 6 +- compiler/rustc_codegen_ssa/src/lib.rs | 10 +- .../rustc_codegen_ssa/src/mir/naked_asm.rs | 2 +- .../rustc_codegen_ssa/src/target_features.rs | 206 +++++++++--------- .../rustc_codegen_ssa/src/traits/backend.rs | 3 +- compiler/rustc_interface/src/util.rs | 38 +++- .../rustc_mir_build/src/check_unsafety.rs | 2 +- .../src/mono_checks/abi_check.rs | 2 +- compiler/rustc_session/src/config/cfg.rs | 2 +- compiler/rustc_session/src/session.rs | 13 +- compiler/rustc_target/src/spec/mod.rs | 4 +- compiler/rustc_target/src/target_features.rs | 30 ++- src/librustdoc/json/conversions.rs | 2 +- src/tools/miri/src/helpers.rs | 2 +- src/tools/miri/src/intrinsics/x86/mod.rs | 2 +- src/tools/miri/src/machine.rs | 4 +- 25 files changed, 200 insertions(+), 175 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/asm.rs b/compiler/rustc_ast_lowering/src/asm.rs index c6124fdfff38c..fd3a00d56fe0b 100644 --- a/compiler/rustc_ast_lowering/src/asm.rs +++ b/compiler/rustc_ast_lowering/src/asm.rs @@ -93,7 +93,7 @@ impl<'hir> LoweringContext<'_, 'hir> { match asm::InlineAsmClobberAbi::parse( asm_arch, &self.tcx.sess.target, - &self.tcx.sess.unstable_target_features, + &self.tcx.sess.internal_target_features, *abi_name, ) { Ok(abi) => { diff --git a/compiler/rustc_codegen_cranelift/src/inline_asm.rs b/compiler/rustc_codegen_cranelift/src/inline_asm.rs index 03fd11afa3f10..0b8eb75972ec0 100644 --- a/compiler/rustc_codegen_cranelift/src/inline_asm.rs +++ b/compiler/rustc_codegen_cranelift/src/inline_asm.rs @@ -404,7 +404,7 @@ impl<'tcx> InlineAssemblyGenerator<'_, 'tcx> { let abi_clobber = InlineAsmClobberAbi::parse( self.arch, &self.tcx.sess.target, - &self.tcx.sess.unstable_target_features, + &self.tcx.sess.internal_target_features, sym::C, ) .unwrap() diff --git a/compiler/rustc_codegen_cranelift/src/lib.rs b/compiler/rustc_codegen_cranelift/src/lib.rs index ba586f83ba30d..c59b77eae4611 100644 --- a/compiler/rustc_codegen_cranelift/src/lib.rs +++ b/compiler/rustc_codegen_cranelift/src/lib.rs @@ -39,6 +39,7 @@ use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::settings::{self, Configurable}; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_codegen_ssa::{CompiledModules, CrateInfo, TargetConfig, back}; +use rustc_data_structures::unord::UnordSet; use rustc_log::tracing::info; use rustc_middle::dep_graph::WorkProductMap; use rustc_session::Session; @@ -170,8 +171,6 @@ impl CodegenBackend for CraneliftCodegenBackend { }, _ => vec![], }; - // FIXME do `unstable_target_features` properly - let unstable_target_features = target_features.clone(); // FIXME(f16_f128): `rustc_codegen_llvm` currently disables support on Windows GNU // targets due to GCC using a different ABI than LLVM. Therefore `f16` and `f128` @@ -186,8 +185,7 @@ impl CodegenBackend for CraneliftCodegenBackend { let has_reliable_f128_math = has_reliable_f16_f128 && sess.target.env == Env::Gnu; TargetConfig { - target_features, - unstable_target_features, + internal_target_features: UnordSet::from_iter(target_features), // `rustc_codegen_cranelift` polyfills functionality not yet // available in Cranelift. has_reliable_f16: has_reliable_f16_f128, diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index 55c721a9706a6..621ee4ce27636 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -85,7 +85,7 @@ use rustc_codegen_ssa::back::write::{ CodegenContext, FatLtoInput, ModuleConfig, SharedEmitter, TargetMachineFactoryFn, ThinLtoInput, }; use rustc_codegen_ssa::base::codegen_crate; -use rustc_codegen_ssa::target_features::cfg_target_feature; +use rustc_codegen_ssa::target_features::internal_target_features; use rustc_codegen_ssa::traits::{CodegenBackend, ExtraBackendMethods, WriteBackendMethods}; use rustc_codegen_ssa::{CompiledModule, CompiledModules, CrateInfo, ModuleCodegen, TargetConfig}; use rustc_data_structures::profiling::SelfProfilerRef; @@ -531,7 +531,7 @@ fn to_gcc_opt_level(optlevel: Option) -> OptimizationLevel { /// Returns the features that should be set in `cfg(target_feature)`. fn target_config(sess: &Session, target_info: &LockedTargetInfo) -> TargetConfig { - let (unstable_target_features, target_features) = cfg_target_feature( + let internal_target_features = internal_target_features( sess, |feature| to_gcc_features(sess, feature), |feature| { @@ -555,8 +555,7 @@ fn target_config(sess: &Session, target_info: &LockedTargetInfo) -> TargetConfig let has_reliable_f128 = target_info.supports_target_dependent_type(CType::Float128); TargetConfig { - target_features, - unstable_target_features, + internal_target_features, // There are no known bugs with GCC support for f16 or f128 has_reliable_f16, has_reliable_f16_math: has_reliable_f16, diff --git a/compiler/rustc_codegen_llvm/src/asm.rs b/compiler/rustc_codegen_llvm/src/asm.rs index d2dfa9a45de8b..fba43bba737e0 100644 --- a/compiler/rustc_codegen_llvm/src/asm.rs +++ b/compiler/rustc_codegen_llvm/src/asm.rs @@ -970,14 +970,14 @@ fn dummy_output_type<'ll>(cx: &CodegenCx<'ll, '_>, reg: InlineAsmRegClass) -> &' Hexagon(HexagonInlineAsmRegClass::vreg) => { // HVX vector register size depends on the HVX mode. // LLVM's "v" constraint requires the exact vector width. - if cx.tcx.sess.unstable_target_features.contains(&sym::hvx_length128b) { + if cx.tcx.sess.internal_target_features.contains(&sym::hvx_length128b) { cx.type_vector(cx.type_i32(), 32) // 1024-bit for 128B mode } else { cx.type_vector(cx.type_i32(), 16) // 512-bit for 64B mode } } Hexagon(HexagonInlineAsmRegClass::vreg_pair) => { - if cx.tcx.sess.unstable_target_features.contains(&sym::hvx_length128b) { + if cx.tcx.sess.internal_target_features.contains(&sym::hvx_length128b) { cx.type_vector(cx.type_i32(), 64) // 2048-bit for 128B mode } else { cx.type_vector(cx.type_i32(), 32) // 1024-bit for 64B mode diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index deef323a2e1f8..a8a0de1c8d347 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -383,9 +383,9 @@ fn packed_stack_attr<'ll>( // The backchain and softfloat flags can be set via -Ctarget-features=... // or via #[target_features(enable = ...)] so we have to check both possibilities - let have_backchain = sess.unstable_target_features.contains(&sym::backchain) + let have_backchain = sess.internal_target_features.contains(&sym::backchain) || function_attributes.iter().any(|feature| feature.name == sym::backchain); - let have_softfloat = sess.unstable_target_features.contains(&sym::soft_float) + let have_softfloat = sess.internal_target_features.contains(&sym::soft_float) || function_attributes.iter().any(|feature| feature.name == sym::soft_float); // If both, backchain and packedstack, are enabled LLVM cannot generate valid function entry points diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index 94883a94f089a..843589fcb265f 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -210,7 +210,7 @@ pub(crate) fn target_machine_factory( let code_model = to_llvm_code_model(sess.code_model()); // This is used to set cfg_has_threads, so all logic must be in this method. - let singlethread = sess.target.singlethread(&sess.target_features); + let singlethread = sess.target.singlethread(&sess.internal_target_features); let triple = SmallCStr::new(&versioned_llvm_target(sess)); let cpu = SmallCStr::new(llvm_util::target_cpu(sess)); diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 9ad14925afb14..ff710cd9f738f 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -7,7 +7,7 @@ use std::{ptr, slice, str}; use libc::c_int; use rustc_codegen_ssa::base::wants_wasm_eh; -use rustc_codegen_ssa::target_features::cfg_target_feature; +use rustc_codegen_ssa::target_features::internal_target_features; use rustc_codegen_ssa::{TargetConfig, target_features}; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::small_c_str::SmallCStr; @@ -314,7 +314,7 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option TargetConfig { let target_machine = create_informational_target_machine(sess, true); - let (unstable_target_features, target_features) = cfg_target_feature( + let internal_target_features = internal_target_features( sess, |feature| { to_llvm_features(sess, feature) @@ -322,9 +322,9 @@ pub(crate) fn target_config(sess: &Session) -> TargetConfig { .unwrap_or_default() }, |feature| { - // This closure determines whether the target CPU has the feature according to LLVM. We do - // *not* consider the `-Ctarget-feature`s here, as that will be handled later in - // `cfg_target_feature`. + // This closure determines whether the target CPU has the feature according to LLVM. We + // do *not* consider the `-Ctarget-feature`s here, as that will be handled later in + // `internal_target_features`. if let Some(feat) = to_llvm_features(sess, feature) { // All the LLVM features this expands to must be enabled. for llvm_feature in feat { @@ -344,8 +344,7 @@ pub(crate) fn target_config(sess: &Session) -> TargetConfig { ); let mut cfg = TargetConfig { - target_features, - unstable_target_features, + internal_target_features, has_reliable_f16: true, has_reliable_f16_math: true, has_reliable_f128: true, @@ -730,7 +729,10 @@ pub(crate) fn global_llvm_features(sess: &Session, only_base_features: bool) -> target_features::flag_to_backend_features(sess, extend_backend_features); } - // We add this in the "base target" so that these show up in `sess.unstable_target_features`. + // `-C` flags that map to LLVM target features. + // We need to include them even with `only_base_features` as this is used to populate + // `sess.internal_target_features` where we very much want them to be present (e.g. the inline + // asm logic uses that to check which registers may be used). llvm_features_by_flags(sess, &mut features); features diff --git a/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs b/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs index dbc0abdb50da8..f8cc07201d10f 100644 --- a/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs +++ b/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs @@ -229,7 +229,7 @@ fn create_elf_raw_dylib_stub(sess: &Session, soname: &str, symbols: &[DllImport] // It is important that the order of reservation matches the order of writing. // The object crate contains many debug asserts that fire if you get this wrong. - let Some((arch, sub_arch)) = sess.target.object_architecture(&sess.unstable_target_features) + let Some((arch, sub_arch)) = sess.target.object_architecture(&sess.internal_target_features) else { sess.dcx().fatal(format!( "raw-dylib is not supported for the architecture `{}`", diff --git a/compiler/rustc_codegen_ssa/src/back/metadata.rs b/compiler/rustc_codegen_ssa/src/back/metadata.rs index 951a60426b5d5..a43bf72b6a27d 100644 --- a/compiler/rustc_codegen_ssa/src/back/metadata.rs +++ b/compiler/rustc_codegen_ssa/src/back/metadata.rs @@ -207,7 +207,7 @@ pub(crate) fn create_object_file(sess: &Session) -> Option Endianness::Big, }; let Some((architecture, sub_architecture)) = - sess.target.object_architecture(&sess.unstable_target_features) + sess.target.object_architecture(&sess.internal_target_features) else { return None; }; @@ -328,12 +328,12 @@ pub(super) fn elf_e_flags(architecture: Architecture, sess: &Session) -> u32 { let mut e_flags: u32 = 0x0; // Check if compression is enabled - if sess.target_features.contains(&sym::zca) { + if sess.internal_target_features.contains(&sym::zca) { e_flags |= elf::EF_RISCV_RVC; } // Check if RVTSO is enabled - if sess.target_features.contains(&sym::ztso) { + if sess.internal_target_features.contains(&sym::ztso) { e_flags |= elf::EF_RISCV_TSO; } diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index 9a42debe1dd97..02ae2d50390cc 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -20,7 +20,7 @@ use std::sync::Arc; use rustc_abi::Size; use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; -use rustc_data_structures::unord::UnordMap; +use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_hir::CRATE_HIR_ID; use rustc_hir::attrs::{CfgEntry, NativeLibKind, WindowsSubsystemKind}; use rustc_hir::def_id::CrateNum; @@ -306,14 +306,12 @@ pub struct CrateInfo { pub exported_symbols_for_lto: Vec, } -/// Target-specific options that get set in `cfg(...)`. +/// Target-specific options that get set in `sess`/`cfg(...)`. /// /// RUSTC_SPECIFIC_FEATURES should be skipped here, those are handled outside codegen. pub struct TargetConfig { - /// Options to be set in `cfg(target_features)`. - pub target_features: Vec, - /// Options to be set in `cfg(target_features)`, but including unstable features. - pub unstable_target_features: Vec, + /// Options to be set in `sess.internal_target_features`. + pub internal_target_features: UnordSet, /// Option for `cfg(target_has_reliable_f16)`, true if `f16` basic arithmetic works. pub has_reliable_f16: bool, /// Option for `cfg(target_has_reliable_f16_math)`, true if `f16` math calls work. diff --git a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs index 131a345fe557d..33cc321ea6d32 100644 --- a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs +++ b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs @@ -151,7 +151,7 @@ fn prefix_and_suffix<'tcx>( let asm_binary_format = &tcx.sess.target.binary_format; let is_arm = tcx.sess.target.arch == Arch::Arm; - let is_thumb = tcx.sess.unstable_target_features.contains(&sym::thumb_mode); + let is_thumb = tcx.sess.internal_target_features.contains(&sym::thumb_mode); let function_sections = tcx.sess.opts.unstable_opts.function_sections.unwrap_or(tcx.sess.target.function_sections); diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index 8f459e5a218d2..8db149fc6df5b 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -131,7 +131,7 @@ pub(crate) fn from_target_feature_attr( /// Computes the set of target features used in a function for the purposes of /// inline assembly. fn asm_target_features(tcx: TyCtxt<'_>, did: DefId) -> &FxIndexSet { - let mut target_features = tcx.sess.unstable_target_features.clone(); + let mut target_features = tcx.sess.internal_target_features.clone(); if tcx.def_kind(did).has_codegen_attrs() { let attrs = tcx.codegen_fn_attrs(did); target_features.extend(attrs.target_features.iter().map(|feature| feature.name)); @@ -164,20 +164,22 @@ pub(crate) fn check_target_feature_trait_unsafe(tcx: TyCtxt<'_>, id: LocalDefId, } } -/// Parse the value of the target spec `features` field or `-Ctarget-feature`, also expanding -/// implied features, and call the closure for each (expanded) Rust feature. If the list contains -/// a syntactically invalid item (not starting with `+`/`-`), the error callback is invoked. +/// Parse the value of the target spec `features` field or `-Ctarget-feature`, calling the closure +/// for each entry in the list, also expanding implied features (but only for actual Rust target +/// features). If the list contains a syntactically invalid item (not starting with `+`/`-`) , the +/// error callback is invoked. fn parse_rust_feature_list<'a>( sess: &'a Session, features: &'a str, err_callback: impl Fn(&'a str), mut callback: impl FnMut( /* base_feature */ &'a str, - /* with_implied */ FxHashSet<&'a str>, + /* with_implied */ Option>, /* enable */ bool, ), ) { - // A cache for the backwards implication map. + // A cache for the forward and backwards feature maps. + let mut features_map: Option> = None; let mut inverse_implied_features: Option>> = None; for feature in features.split(',') { @@ -187,13 +189,30 @@ fn parse_rust_feature_list<'a>( continue; } - callback(base_feature, sess.target.implied_target_features(base_feature), true) + let features_map = + features_map.get_or_insert_with(|| sess.target.rust_target_features_map()); + + if !features_map.contains_key(&base_feature) { + callback(base_feature, None, true); + continue; + } + + let implied_features = sess.target.implied_target_features(base_feature, &features_map); + callback(base_feature, Some(implied_features), true) } else if let Some(base_feature) = feature.strip_prefix('-') { // Skip features that are not target features, but rustc features. if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) { continue; } + let features_map = + features_map.get_or_insert_with(|| sess.target.rust_target_features_map()); + + if !features_map.contains_key(&base_feature) { + callback(base_feature, None, false); + continue; + } + // If `f1` implies `f2`, then `!f2` implies `!f1` -- this is standard logical // contraposition. So we have to find all the reverse implications of `base_feature` and // disable them, too. @@ -210,10 +229,10 @@ fn parse_rust_feature_list<'a>( // Inverse implied target features have their own inverse implied target features, so we // traverse the map until there are no more features to add. - let mut features = FxHashSet::default(); + let mut implied_features = FxHashSet::default(); let mut new_features = vec![base_feature]; while let Some(new_feature) = new_features.pop() { - if features.insert(new_feature) { + if implied_features.insert(new_feature) { if let Some(implied_features) = inverse_implied_features.get(&new_feature) { #[allow(rustc::potential_query_instability)] new_features.extend(implied_features) @@ -221,16 +240,15 @@ fn parse_rust_feature_list<'a>( } } - callback(base_feature, features, false) + callback(base_feature, Some(implied_features), false) } else if !feature.is_empty() { err_callback(feature) } } } -/// Utility function for a codegen backend to compute `cfg(target_feature)`, or more specifically, -/// to populate `sess.unstable_target_features` and `sess.target_features` (these are the first and -/// 2nd component of the return value, respectively). +/// Utility function for a codegen backend to compute the set of all actually enabled Rust target +/// features (which will be stored in `sess.internal_target_features`). /// /// `to_backend_features` converts a Rust feature name into a list of backend feature names; this is /// used for diagnostic purposes only. @@ -242,15 +260,15 @@ fn parse_rust_feature_list<'a>( /// to target features. /// /// We do not have to worry about RUSTC_SPECIFIC_FEATURES here, those are handled elsewhere. -pub fn cfg_target_feature<'a, const N: usize>( +pub fn internal_target_features<'a, const N: usize>( sess: &Session, to_backend_features: impl Fn(&'a str) -> SmallVec<[&'a str; N]>, mut target_base_has_feature: impl FnMut(&str) -> bool, -) -> (Vec, Vec) { - let known_features = sess.target.rust_target_features(); +) -> UnordSet { + let features_map = sess.target.rust_target_features_map(); - // Compute which of the known target features are enabled in the 'base' target machine. We only - // consider "supported" features; "forbidden" features are not reflected in `cfg` as of now. + // Compute which of the known target features are enabled in the 'base' target machine: for + // every Rust target feature, ask the backend if it is enabled. let mut features: UnordSet = sess .target .rust_target_features() @@ -263,10 +281,14 @@ pub fn cfg_target_feature<'a, const N: usize>( // // Iteration order is irrelevant because we're collecting into an `UnordSet`. #[allow(rustc::potential_query_instability)] - sess.target.implied_target_features(base_feature).into_iter().map(|f| Symbol::intern(f)) + sess.target + .implied_target_features(base_feature, &features_map) + .into_iter() + .map(|f| Symbol::intern(f)) }) .collect(); + // State gathered for "tied features" check. let mut enabled_disabled_features = FxHashMap::default(); // Add enabled and remove disabled features. @@ -278,37 +300,23 @@ pub fn cfg_target_feature<'a, const N: usize>( sess.dcx().emit_warn(diagnostics::UnknownCTargetFeaturePrefix { feature }); }, |base_feature, new_features, enable| { - // Iteration order is irrelevant since this only influences an `FxHashMap`. - #[allow(rustc::potential_query_instability)] - enabled_disabled_features.extend(new_features.iter().map(|&s| (s, enable))); - - // Iteration order is irrelevant since this only influences an `UnordSet`. - #[allow(rustc::potential_query_instability)] - if enable { - features.extend(new_features.into_iter().map(|f| Symbol::intern(f))); - } else { - // Remove `new_features` from `features`. - for new in new_features { - features.remove(&Symbol::intern(new)); - } - } - - // Check feature validity. - let feature_state = known_features.iter().find(|&&(v, _, _)| v == base_feature); - match feature_state { + match features_map.get(base_feature) { None => { - // This is definitely not a valid Rust feature name. Maybe it is a backend - // feature name? If so, give a better error message. - let rust_feature = known_features.iter().find_map(|&(rust_feature, _, _)| { - let backend_features = to_backend_features(rust_feature); - if backend_features.contains(&base_feature) - && !backend_features.contains(&rust_feature) - { - Some(rust_feature) - } else { - None - } - }); + // This is definitely not a valid Rust feature name. We do not add it to + // `features`. Maybe it is a backend feature name? If so, give a better error + // message. + let rust_feature = sess.target.rust_target_features().iter().find_map( + |&(rust_feature, _, _)| { + let backend_features = to_backend_features(rust_feature); + if backend_features.contains(&base_feature) + && !backend_features.contains(&rust_feature) + { + Some(rust_feature) + } else { + None + } + }, + ); let unknown_feature = if let Some(rust_feature) = rust_feature { diagnostics::UnknownCTargetFeature { feature: base_feature, @@ -322,7 +330,25 @@ pub fn cfg_target_feature<'a, const N: usize>( }; sess.dcx().emit_warn(unknown_feature); } - Some((_, stability, _)) => { + Some((stability, _)) => { + let new_features = new_features.unwrap(); + // Add feature to our set -- only if it is actually a recognized feature. + // Iteration order is irrelevant since this only influences an `FxHashMap`. + #[allow(rustc::potential_query_instability)] + enabled_disabled_features.extend(new_features.iter().map(|&s| (s, enable))); + + // Iteration order is irrelevant since this only influences an `UnordSet`. + #[allow(rustc::potential_query_instability)] + if enable { + features.extend(new_features.into_iter().map(|f| Symbol::intern(f))); + } else { + // Remove `new_features` from `features`. + for new in new_features { + features.remove(&Symbol::intern(new)); + } + } + + // Check feature stability. if let Stability::Forbidden { reason, hard_error } = stability { let diag = diagnostics::ForbiddenCTargetFeature { feature: base_feature, @@ -363,34 +389,11 @@ pub fn cfg_target_feature<'a, const N: usize>( }); } - // Filter enabled features based on feature gates. - let f = |allow_unstable| { - sess.target - .rust_target_features() - .iter() - .filter_map(|(feature, gate, _)| { - // The `allow_unstable` set is used by rustc internally to determine which target - // features are truly available, so we want to return even perma-unstable - // "forbidden" features. - if allow_unstable - || (gate.in_cfg() - && (sess.is_nightly_build() - || gate.requires_nightly(/* in_cfg */ true).is_none())) - { - Some(Symbol::intern(feature)) - } else { - None - } - }) - .filter(|feature| features.contains(&feature)) - .collect() - }; - - (f(true), f(false)) + features } /// Given a map from target_features to whether they are enabled or disabled, ensure only valid -/// combinations are allowed. +/// combinations are allowed. Returns `Some` if a violation is found. pub fn check_tied_features( sess: &Session, features: &FxHashMap<&str, bool>, @@ -416,8 +419,6 @@ pub fn target_spec_to_backend_features<'a>( sess: &'a Session, mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool), ) { - let mut rust_features = vec![]; - // This check handles SM versions that defaults (by LLVM) to unsupported (by Rust) PTX ISA versions. // sm_70, sm_72 and sm_75 defaults to PTX ISA versions with major version 6, while sm_80 default to 7.0 if sess.target.arch == Arch::Nvptx64 @@ -426,7 +427,7 @@ pub fn target_spec_to_backend_features<'a>( None | Some("sm_70") | Some("sm_72") | Some("sm_75") ) { - rust_features.push((true, "ptx70")); + extend_backend_features("ptx70", true); } // Compute implied features @@ -435,20 +436,18 @@ pub fn target_spec_to_backend_features<'a>( &sess.target.features, /* err_callback */ |feature| { - panic!("Target spec contains invalid feature {feature}"); + panic!("Target spec contains invalid feature {feature} (missing `+`/`-` prefix)"); }, - |_base_feature, new_features, enable| { - // FIXME emit an error for unknown features like cfg_target_feature would for -Ctarget-feature - rust_features.extend( - UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)), - ); + |base_feature, new_features, enable| { + // FIXME emit an error for unknown features in the target spec like + // internal_target_features would for -Ctarget-feature. + let new_features = + new_features.unwrap_or_else(|| FxHashSet::from_iter(std::iter::once(base_feature))); + for new_feature in UnordSet::from(new_features).to_sorted_stable_ord().iter() { + extend_backend_features(new_feature, enable); + } }, ); - - // Add this to the backend features. - for (enable, feature) in rust_features { - extend_backend_features(feature, enable); - } } /// Translates the `-Ctarget-feature` flag into a backend target feature list. @@ -459,26 +458,22 @@ pub fn flag_to_backend_features<'a>( sess: &'a Session, mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool), ) { - // Compute implied features - let mut rust_features = vec![]; parse_rust_feature_list( sess, &sess.opts.cg.target_feature, /* err_callback */ |_feature| { - // Errors are already emitted in `cfg_target_feature`; avoid duplicates. + // Errors are already emitted in `internal_target_features`; avoid duplicates. }, - |_base_feature, new_features, enable| { - rust_features.extend( - UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)), - ); + |base_feature, new_features, enable| { + // Forward unknown features to the backend as that's what we have always done. + let new_features = + new_features.unwrap_or_else(|| FxHashSet::from_iter(std::iter::once(base_feature))); + for new_feature in UnordSet::from(new_features).to_sorted_stable_ord().iter() { + extend_backend_features(new_feature, enable); + } }, ); - - // Add this to the backend features. - for (enable, feature) in rust_features { - extend_backend_features(feature, enable); - } } /// Computes the backend target features to be added to account for retpoline flags. @@ -553,13 +548,18 @@ pub(crate) fn provide(providers: &mut Providers) { .target .rust_target_features() .iter() - .map(|(a, b, _)| (a.to_string(), *b)) + .map(|(feat, stab, _)| (feat.to_string(), *stab)) .collect() } }, implied_target_features: |tcx, feature: Symbol| { + if tcx.sess.opts.actually_rustdoc { + // We can't handle implication when we are mixing all targets. + return vec![feature]; + } + let features_map = tcx.sess.target.rust_target_features_map(); let feature = feature.as_str(); - UnordSet::from(tcx.sess.target.implied_target_features(feature)) + UnordSet::from(tcx.sess.target.implied_target_features(feature, &features_map)) .into_sorted_stable_ord() .into_iter() .map(|s| Symbol::intern(s)) diff --git a/compiler/rustc_codegen_ssa/src/traits/backend.rs b/compiler/rustc_codegen_ssa/src/traits/backend.rs index 6014f1af4bfc3..1d63490eab654 100644 --- a/compiler/rustc_codegen_ssa/src/traits/backend.rs +++ b/compiler/rustc_codegen_ssa/src/traits/backend.rs @@ -44,8 +44,7 @@ pub trait CodegenBackend { /// `target_feature` and support for unstable float types. fn target_config(&self, _sess: &Session) -> TargetConfig { TargetConfig { - target_features: vec![], - unstable_target_features: vec![], + internal_target_features: Default::default(), // `true` is used as a default so backends need to acknowledge when they do not // support the float types, rather than accidentally quietly skipping all tests. has_reliable_f16: true, diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index 019c7ccfe979a..58a001ab5b1e9 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -11,7 +11,7 @@ use rustc_ast as ast; use rustc_attr_parsing::ShouldEmit; use rustc_codegen_ssa::back::archive::{ArArchiveBuilderBuilder, ArchiveBuilderBuilder}; use rustc_codegen_ssa::back::link::link_binary; -use rustc_codegen_ssa::target_features::cfg_target_feature; +use rustc_codegen_ssa::target_features::internal_target_features; use rustc_codegen_ssa::traits::CodegenBackend; use rustc_codegen_ssa::{CompiledModules, CrateInfo, TargetConfig}; use rustc_data_structures::base_n::{CASE_INSENSITIVE, ToBaseN}; @@ -50,10 +50,27 @@ pub(crate) fn add_configuration( let tf = sym::target_feature; let tf_cfg = codegen_backend.target_config(sess); - sess.unstable_target_features.extend(tf_cfg.unstable_target_features.iter().copied()); - sess.target_features.extend(tf_cfg.target_features.iter().copied()); + // Add some of the target features to `cfg`. + cfg.extend( + sess.target + .rust_target_features() + .iter() + .filter_map(|(feature, gate, _)| { + if gate.in_cfg() + && (sess.is_nightly_build() + || gate.requires_nightly(/* in_cfg */ true).is_none()) + { + Some(Symbol::intern(feature)) + } else { + None + } + }) + .filter(|feature| tf_cfg.internal_target_features.contains(&feature)) + .map(|feature| (sym::target_feature, Some(feature))), + ); - cfg.extend(tf_cfg.target_features.into_iter().map(|feat| (tf, Some(feat)))); + // Store all of them in the session. + sess.internal_target_features.extend(tf_cfg.internal_target_features.into_sorted_stable_ord()); if tf_cfg.has_reliable_f16 { cfg.insert((sym::target_has_reliable_f16, None)); @@ -74,10 +91,10 @@ pub(crate) fn add_configuration( } /// Ensures that all target features required by the ABI are present. -/// Must be called after `unstable_target_features` has been populated! +/// Must be called after `internal_target_features` has been populated! pub(crate) fn check_abi_required_features(sess: &Session) { let abi_feature_constraints = sess.target.abi_required_features(); - // We check this against `unstable_target_features` as that is conveniently already + // We check this against `internal_target_features` as that is conveniently already // back-translated to rustc feature names, taking into account `-Ctarget-cpu` and `-Ctarget-feature`. // Just double-check that the features we care about are actually on our list. for feature in @@ -90,13 +107,13 @@ pub(crate) fn check_abi_required_features(sess: &Session) { } for feature in abi_feature_constraints.required { - if !sess.unstable_target_features.contains(&Symbol::intern(feature)) { + if !sess.internal_target_features.contains(&Symbol::intern(feature)) { sess.dcx() .emit_warn(diagnostics::AbiRequiredTargetFeature { feature, enabled: "enabled" }); } } for feature in abi_feature_constraints.incompatible { - if sess.unstable_target_features.contains(&Symbol::intern(feature)) { + if sess.internal_target_features.contains(&Symbol::intern(feature)) { sess.dcx() .emit_warn(diagnostics::AbiRequiredTargetFeature { feature, enabled: "disabled" }); } @@ -374,7 +391,7 @@ impl CodegenBackend for DummyCodegenBackend { } let abi_required_features = sess.target.abi_required_features(); - let (target_features, unstable_target_features) = cfg_target_feature::<0>( + let internal_target_features = internal_target_features::<0>( sess, |_feature| Default::default(), |feature| { @@ -387,8 +404,7 @@ impl CodegenBackend for DummyCodegenBackend { ); TargetConfig { - target_features, - unstable_target_features, + internal_target_features, has_reliable_f16: true, has_reliable_f16_math: true, has_reliable_f128: true, diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs index 70e9129ffee3f..69590fc351320 100644 --- a/compiler/rustc_mir_build/src/check_unsafety.rs +++ b/compiler/rustc_mir_build/src/check_unsafety.rs @@ -448,7 +448,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { let build_enabled = self .tcx .sess - .target_features + .internal_target_features .iter() .copied() .filter(|feature| missing.contains(feature)) diff --git a/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs b/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs index 4479ce2dba08b..173595de5c8c2 100644 --- a/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs +++ b/compiler/rustc_monomorphize/src/mono_checks/abi_check.rs @@ -57,7 +57,7 @@ fn do_check_simd_vector_abi<'tcx>( ) { let codegen_attrs = tcx.codegen_fn_attrs(def_id); let have_feature = |feat: Symbol| { - let target_feats = tcx.sess.unstable_target_features.contains(&feat); + let target_feats = tcx.sess.internal_target_features.contains(&feat); let fn_feats = codegen_attrs.target_features.iter().any(|x| x.name == feat); target_feats || fn_feats }; diff --git a/compiler/rustc_session/src/config/cfg.rs b/compiler/rustc_session/src/config/cfg.rs index 84a26af6b54ce..e5c874503a00f 100644 --- a/compiler/rustc_session/src/config/cfg.rs +++ b/compiler/rustc_session/src/config/cfg.rs @@ -304,7 +304,7 @@ pub(crate) fn default_configuration(sess: &Session) -> Cfg { } } - if !sess.target.singlethread(&sess.target_features) { + if !sess.target.singlethread(&sess.internal_target_features) { ins_none!(sym::target_has_threads); } diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index aea36bf44f28d..ca0bba589f79a 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -375,11 +375,11 @@ pub struct Session { /// Architecture to use for interpreting asm!. pub asm_arch: Option, - /// Set of enabled features for the current target. - pub target_features: FxIndexSet, - - /// Set of enabled features for the current target, including unstable ones. - pub unstable_target_features: FxIndexSet, + /// Set of actually enabled features for the current target, including ones that are not + /// in `cfg(target_feature)` because they are unstable or forbidden. + /// This is used by the compiler itself when it needs to know which target features are actually + /// going to be enabled in the backend. + pub internal_target_features: FxIndexSet, /// The version of the rustc process, possibly including a commit hash and description. pub cfg_version: &'static str, @@ -1388,8 +1388,7 @@ pub fn build_session( ctfe_backtrace, miri_unleashed_features: Lock::new(Default::default()), asm_arch, - target_features: Default::default(), - unstable_target_features: Default::default(), + internal_target_features: Default::default(), cfg_version, using_internal_features, env_depinfo: Default::default(), diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index a747b0aec7b28..9af73eb1e3259 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -3835,7 +3835,7 @@ impl Target { pub fn object_architecture( &self, - unstable_target_features: &FxIndexSet, + internal_target_features: &FxIndexSet, ) -> Option<(object::Architecture, Option)> { use object::Architecture; Some(match self.arch { @@ -3878,7 +3878,7 @@ impl Target { Arch::RiscV32 => (Architecture::Riscv32, None), Arch::RiscV64 => (Architecture::Riscv64, None), Arch::Sparc => { - if unstable_target_features.contains(&sym::v8plus) { + if internal_target_features.contains(&sym::v8plus) { // Target uses V8+, aka EM_SPARC32PLUS, aka 64-bit V9 but in 32-bit mode (Architecture::Sparc32Plus, None) } else { diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs index ce09972396e56..9473a583c6586 100644 --- a/compiler/rustc_target/src/target_features.rs +++ b/compiler/rustc_target/src/target_features.rs @@ -45,7 +45,8 @@ use rustc_span::{Symbol, sym}; use crate::spec::{Arch, FloatAbi, LlvmAbi, RustcAbi, Target}; -/// Features that control behaviour of rustc, rather than the codegen. +/// Features that control behaviour of rustc, rather than the codegen. Not to be included in +/// `cfg(target_feature)`, `sess.internal_target_features`, or the backend's feature list. /// These exist globally and are not in the target-specific lists below. pub const RUSTC_SPECIFIC_FEATURES: &[&str] = &["crt-static"]; @@ -1152,6 +1153,16 @@ impl Target { } } + /// Computes a map mapping each Rust target feature to the features it implies. + pub fn rust_target_features_map( + &self, + ) -> FxHashMap<&'static str, (Stability, ImpliedFeatures)> { + self.rust_target_features() + .iter() + .map(|&(f, s, i)| (f, (s, i))) + .collect::>() + } + pub fn features_for_correct_fixed_length_vector_abi(&self) -> &'static [(u64, &'static str)] { match &self.arch { Arch::X86 | Arch::X86_64 => X86_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI, @@ -1194,19 +1205,22 @@ impl Target { } // Note: the returned set includes `base_feature`. - pub fn implied_target_features<'a>(&self, base_feature: &'a str) -> FxHashSet<&'a str> { - let implied_features = - self.rust_target_features().iter().map(|(f, _, i)| (f, i)).collect::>(); - + #[track_caller] + pub fn implied_target_features<'a>( + &self, + base_feature: &'a str, + target_features_map: &FxHashMap<&'static str, (Stability, ImpliedFeatures)>, + ) -> FxHashSet<&'a str> { // Implied target features have their own implied target features, so we traverse the // map until there are no more features to add. let mut features = FxHashSet::default(); let mut new_features = vec![base_feature]; while let Some(new_feature) = new_features.pop() { if features.insert(new_feature) { - if let Some(implied_features) = implied_features.get(&new_feature) { - new_features.extend(implied_features.iter().copied()) - } + let (_, implied_features) = target_features_map + .get(&new_feature) + .unwrap_or_else(|| panic!("encountered non-Rust target feature {new_feature}")); + new_features.extend(implied_features.iter().copied()); } } features diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 7e46b2f593e49..0512a3daac46e 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -1291,7 +1291,7 @@ fn format_integer_type(it: rustc_abi::IntegerType) -> String { pub(super) fn target(sess: &rustc_session::Session) -> Target { // Build a set of which features are enabled on this target let globally_enabled_features: FxHashSet<&str> = - sess.unstable_target_features.iter().map(|name| name.as_str()).collect(); + sess.internal_target_features.iter().map(|name| name.as_str()).collect(); // Build a map of target feature stability by feature name use rustc_target::target_features::Stability; diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 8dc6b5f07b92e..ce66a2b8b29c7 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -945,7 +945,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { target_feature: &str, ) -> InterpResult<'tcx, ()> { let this = self.eval_context_ref(); - if !this.tcx.sess.unstable_target_features.contains(&Symbol::intern(target_feature)) { + if !this.tcx.sess.internal_target_features.contains(&Symbol::intern(target_feature)) { throw_ub_format!( "attempted to call intrinsic `{intrinsic}` that requires missing target feature {target_feature}" ); diff --git a/src/tools/miri/src/intrinsics/x86/mod.rs b/src/tools/miri/src/intrinsics/x86/mod.rs index d76d35cb722bc..25361a6435b0a 100644 --- a/src/tools/miri/src/intrinsics/x86/mod.rs +++ b/src/tools/miri/src/intrinsics/x86/mod.rs @@ -65,7 +65,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "sse2.pause" => { let [] = this.check_shim_sig_unadjusted(link_name, args)?; // Only exhibit the spin-loop hint behavior when SSE2 is enabled. - if this.tcx.sess.unstable_target_features.contains(&Symbol::intern("sse2")) { + if this.tcx.sess.internal_target_features.contains(&Symbol::intern("sse2")) { this.yield_active_thread(); } } diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index f476614992041..4ba10ce4612b4 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -1203,14 +1203,14 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { if attrs .target_features .iter() - .any(|feature| !ecx.tcx.sess.target_features.contains(&feature.name)) + .any(|feature| !ecx.tcx.sess.internal_target_features.contains(&feature.name)) { let unavailable = attrs .target_features .iter() .filter(|&feature| { feature.kind != TargetFeatureKind::Implied - && !ecx.tcx.sess.target_features.contains(&feature.name) + && !ecx.tcx.sess.internal_target_features.contains(&feature.name) }) .fold(String::new(), |mut s, feature| { if !s.is_empty() { From 78de456ed2570e84503931f7cdc7d7351427290b Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 6 Aug 2026 14:53:46 +0200 Subject: [PATCH 073/100] derive(Diagnostic): link to proper docs --- compiler/rustc_macros/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs index 399f20ebfe1eb..2f4e5606cd555 100644 --- a/compiler/rustc_macros/src/lib.rs +++ b/compiler/rustc_macros/src/lib.rs @@ -180,7 +180,7 @@ decl_derive!( decl_derive!([Lift, attributes(lift)] => lift::lift_derive); decl_derive!( [Diagnostic, attributes( - // struct attributes + // struct and field attributes diag, help, help_once, @@ -194,7 +194,9 @@ decl_derive!( suggestion, suggestion_short, suggestion_hidden, - suggestion_verbose)] => diagnostics::diagnostic_derive + suggestion_verbose)] => + #[doc = "See "] + diagnostics::diagnostic_derive ); decl_derive!( [Subdiagnostic, attributes( From 6e9475f1bf3baf4a2afc5bbe7937888796aba021 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Thu, 6 Aug 2026 15:18:17 +0200 Subject: [PATCH 074/100] Derive attribute parser debug impls --- compiler/rustc_attr_parsing/src/parser.rs | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs index 76587ba9f0ead..5f1fc8ba90d5e 100644 --- a/compiler/rustc_attr_parsing/src/parser.rs +++ b/compiler/rustc_attr_parsing/src/parser.rs @@ -315,6 +315,7 @@ impl MetaItemOrLitParser { /// `= value` part /// /// The syntax of `MetaItems` can be found at +#[derive(Debug)] pub struct MetaItemParser { path: OwnedPathParser, args: ArgParser, @@ -325,15 +326,6 @@ pub struct MetaItemParser { args_checked: AtomicBool, } -impl Debug for MetaItemParser { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("MetaItemParser") - .field("path", &self.path) - .field("args", &self.args) - .finish() - } -} - impl MetaItemParser { /// For a single-segment meta item, returns its name; otherwise, returns `None`. pub fn ident(&self) -> Option { @@ -385,23 +377,13 @@ impl MetaItemParser { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct NameValueParser { pub eq_span: Span, value: MetaItemLit, pub value_span: Span, } -impl Debug for NameValueParser { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("NameValueParser") - .field("eq_span", &self.eq_span) - .field("value", &self.value) - .field("value_span", &self.value_span) - .finish() - } -} - impl NameValueParser { pub fn value_as_lit(&self) -> &MetaItemLit { &self.value From a1b86a7edfd0365463ea4865bdfdd1ae8f47638e Mon Sep 17 00:00:00 2001 From: rabindra789 Date: Mon, 3 Aug 2026 19:09:42 +0530 Subject: [PATCH 075/100] codegen: classify localized MSVC linker progress as linker_info link.exe progress messages (e.g. "Creating library ...") are detected by matching their English text, which fails when the English language pack is not installed and the output is localized despite VSLANG=1033. Since all actual warnings and errors carry a locale-independent LNK#### code, classify every line without one as linker_info instead of linker_messages. Diagnostics are recognized by their structured form, `LINK : warning LNK####:`: the code must be followed by a `:` that is the second colon in the line, so the matcher cannot accidentally hit file names. The one code-bearing informational line, LNK6004 ("performing full link"), keeps the exception that was previously handled by matching its English text. --- compiler/rustc_codegen_ssa/src/back/link.rs | 42 ++++++++---- .../fake-linker.rs | 22 ++++++ .../msvc-localized-linker-output/main.rs | 1 + .../msvc-localized-linker-output/rmake.rs | 67 +++++++++++++++++++ 4 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 tests/run-make/msvc-localized-linker-output/fake-linker.rs create mode 100644 tests/run-make/msvc-localized-linker-output/main.rs create mode 100644 tests/run-make/msvc-localized-linker-output/rmake.rs diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 8cbf3647f5630..b9e3ba4ab9ada 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -1073,27 +1073,43 @@ fn report_linker_output( escape_string(output.trim().as_bytes()) } + fn has_lnk_code(line: &str) -> bool { + // link.exe diagnostics are structured as `LINK : warning LNK####:` or + // `LINK : fatal error LNK####:`. The code is always followed by a `:` + // that is the second colon in the line, so matching that structure + // instead of scanning for `LNK####` anywhere avoids false positives on + // file names. + let Some((code_colon, _)) = line.match_indices(':').nth(1) else { + return false; + }; + let Some(code) = code_colon.checked_sub(7) else { + return false; + }; + let code = &line.as_bytes()[code..code_colon]; + code.starts_with(b"LNK") && code[3..].iter().all(u8::is_ascii_digit) + } + if is_msvc_link_exe(sess) { info!("inferred MSVC link.exe"); escaped_stdout = for_each(&stdout, |line, output| { - // Hide some progress messages from link.exe that we don't care about. - // See https://github.com/chromium/chromium/blob/bfa41e41145ffc85f041384280caf2949bb7bd72/build/toolchain/win/tool_wrapper.py#L144-L146 - // When incremental linking is enabled and an .ilk exists, but its associated .exe is - // missing, link.exe prints the path of the missing .exe followed by: + // Hide progress messages from link.exe that we don't care about. + // These include localized variants of the English messages (e.g. + // "Creating library ..."), which rustc cannot recognize by text + // without the English language pack. + // See https://github.com/rust-lang/rust/issues/159133 + // When incremental linking is enabled and an .ilk exists, but its + // associated .exe is missing, link.exe prints the path of the + // missing .exe followed by: let ilk_but_no_exe = "not found or not built by the last incremental link; performing full link"; - let trimmed = line.trim_start(); - if trimmed.starts_with("Creating library") - || trimmed.starts_with("Generating code") - || trimmed.starts_with("Finished generating code") - || trimmed.ends_with(ilk_but_no_exe) - { - linker_info += line; - linker_info += "\r\n"; - } else { + // LNK6004 is the one code-bearing line that is still informational. + if has_lnk_code(line) && !line.ends_with(ilk_but_no_exe) { *output += line; *output += "\r\n" + } else { + linker_info += line; + linker_info += "\r\n"; } }); } else if is_macos_linker(sess) { diff --git a/tests/run-make/msvc-localized-linker-output/fake-linker.rs b/tests/run-make/msvc-localized-linker-output/fake-linker.rs new file mode 100644 index 0000000000000..2cbb68c4518bf --- /dev/null +++ b/tests/run-make/msvc-localized-linker-output/fake-linker.rs @@ -0,0 +1,22 @@ +fn main() { + // Simulate a localized (e.g. Japanese) `link.exe`, as printed when the + // English language pack is not installed and `VSLANG=1033` has no effect. + // This is "Creating library foo.dll.lib and object foo.dll.exp" in Japanese. + println!("ライブラリ foo.dll.lib とオブジェクト foo.dll.exp を作成中"); + // A file name containing an `LNK####`-looking fragment must not be + // mistaken for a diagnostic, which is why the matcher requires the + // structured `LINK : warning LNK####:` form. + println!("LNK2001.lib: progress message, not a diagnostic"); + for arg in std::env::args() { + if arg == "run_make_lnk" { + // Real diagnostics are structured as `LINK : warning LNK####:`. + println!("LINK : warning LNK2001: unresolved external symbol foo"); + // The one code-bearing informational line has no `LINK : ` prefix + // and keeps the exception that classifies it as `linker_info`. + println!( + "LNK6004: 'foo.exe' not found or not built by the last incremental link; \ + performing full link" + ); + } + } +} diff --git a/tests/run-make/msvc-localized-linker-output/main.rs b/tests/run-make/msvc-localized-linker-output/main.rs new file mode 100644 index 0000000000000..f328e4d9d04c3 --- /dev/null +++ b/tests/run-make/msvc-localized-linker-output/main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/tests/run-make/msvc-localized-linker-output/rmake.rs b/tests/run-make/msvc-localized-linker-output/rmake.rs new file mode 100644 index 0000000000000..3d0e4c0d7b61d --- /dev/null +++ b/tests/run-make/msvc-localized-linker-output/rmake.rs @@ -0,0 +1,67 @@ +//@ only-msvc +//@ ignore-cross-compile (need to run the fake link.exe on the host) + +//! Tests that localized (non-English) MSVC `link.exe` progress messages are +//! classified as `linker_info`, not `linker_messages`. +//! +//! `link.exe` is hardcoded by rustc to run with `VSLANG=1033`, which only works +//! when an English language pack is installed. Without it, messages like +//! "Creating library ..." are printed in another language, and the English +//! string matching that used to detect them fails. Since all real diagnostics +//! carry a locale-independent `LNK####` code, printed in the structured +//! `LINK : warning LNK####:` form, any line without one is informational, no +//! matter the language it was printed in. + +use run_make_support::{bare_rustc, rustc, target}; + +fn main() { + // rustc prepends the sysroot's tools bin directory to the linker's `PATH`, + // which bare names like `link.exe` are resolved against. Put the fake + // `link.exe` there so it wins over the real linker; `-L` below keeps std + // available from the real sysroot. + let fake_sysroot = std::env::current_dir().unwrap().join("fake-sysroot"); + let tools_bin = fake_sysroot.join(format!("lib/rustlib/{}/bin", target())); + std::fs::create_dir_all(&tools_bin).unwrap(); + rustc().arg("fake-linker.rs").output(tools_bin.join("link.exe")).run(); + + let real_libdir = rustc().print("target-libdir").run().stdout_utf8(); + let real_libdir = real_libdir.trim(); + + let fake_link = |extra: &[&str]| { + let mut r = bare_rustc(); + r.input("main.rs") + .output("main") + .arg(format!("--sysroot={}", fake_sysroot.display())) + .arg(format!("-L{real_libdir}")) + // Matched by name against the linker's `PATH`, so the fake in the + // tools bin directory is used instead of the real VS linker. + .arg("-Clinker=link.exe") + // Overrides `rust.lld=true` on CI. + .arg("-Clinker-flavor=msvc"); + for a in extra { + r.arg(a); + } + r + }; + + // The localized progress line must not warn by default. + fake_link(&[]) + .run() + .assert_stderr_not_contains("linker stdout") + .assert_stderr_not_contains("ライブラリ foo.dll.lib とオブジェクト foo.dll.exp を作成中"); + + // It is still visible through `linker_info`, and must not be misclassified + // as `linker_messages`. + fake_link(&["-Wlinker_info", "-Dlinker_messages"]) // Fail if the message is misclassified. + .run() + .assert_stderr_contains("ライブラリ foo.dll.lib とオブジェクト foo.dll.exp を作成中"); + + // Real diagnostics keep their `LNK####` code and still warn. + fake_link(&["-Clink-arg=run_make_lnk"]) + .run() + .assert_stderr_contains( + "warning: linker stdout: LINK : warning LNK2001: unresolved external symbol foo", + ) + // The informational LNK6004 line stays hidden. + .assert_stderr_not_contains("LNK6004"); +} From 6253cca669bf76acdae402f17775101ef894bdb2 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 5 Aug 2026 08:52:33 +0200 Subject: [PATCH 076/100] rename 'forbidden' target features to 'internal-only' --- compiler/rustc_codegen_ssa/src/diagnostics.rs | 4 +- .../rustc_codegen_ssa/src/target_features.rs | 15 ++- compiler/rustc_session/src/session.rs | 2 +- compiler/rustc_target/src/target_features.rs | 104 +++++++++++------- 4 files changed, 77 insertions(+), 48 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/diagnostics.rs b/compiler/rustc_codegen_ssa/src/diagnostics.rs index 6b182d795a9ec..cb746a8eed90d 100644 --- a/compiler/rustc_codegen_ssa/src/diagnostics.rs +++ b/compiler/rustc_codegen_ssa/src/diagnostics.rs @@ -1100,7 +1100,7 @@ pub(crate) struct TargetFeatureSafeTrait { #[derive(Diagnostic)] #[diag("target feature `{$feature}` cannot be enabled with `#[target_feature]`: {$reason}")] -pub(crate) struct ForbiddenTargetFeatureAttr<'a> { +pub(crate) struct InternalOnlyTargetFeatureAttr<'a> { #[primary_span] pub span: Span, pub feature: &'a str, @@ -1233,7 +1233,7 @@ pub(crate) struct UnstableCTargetFeature<'a> { #[derive(Diagnostic)] #[diag("target feature `{$feature}` cannot be {$enabled} with `-Ctarget-feature`: {$reason}")] -pub(crate) struct ForbiddenCTargetFeature<'a> { +pub(crate) struct InternalOnlyCTargetFeature<'a> { pub feature: &'a str, pub enabled: &'a str, pub reason: &'a str, diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index 8db149fc6df5b..7705bb72bd890 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -72,7 +72,7 @@ pub(crate) fn from_target_feature_attr( // Only allow target features whose feature gates have been enabled // and which are permitted to be toggled. if let Err(reason) = stability.toggle_allowed() { - tcx.dcx().emit_err(diagnostics::ForbiddenTargetFeatureAttr { + tcx.dcx().emit_err(diagnostics::InternalOnlyTargetFeatureAttr { span: feature_span, feature: feature_str, reason, @@ -107,7 +107,7 @@ pub(crate) fn from_target_feature_attr( diagnostics::Aarch64SoftfloatNeon, ); } else { - tcx.dcx().emit_err(diagnostics::ForbiddenTargetFeatureAttr { + tcx.dcx().emit_err(diagnostics::InternalOnlyTargetFeatureAttr { span: feature_span, feature: name.as_str(), reason: "this feature is incompatible with the target ABI", @@ -349,8 +349,8 @@ pub fn internal_target_features<'a, const N: usize>( } // Check feature stability. - if let Stability::Forbidden { reason, hard_error } = stability { - let diag = diagnostics::ForbiddenCTargetFeature { + if let Stability::InternalOnly { reason, hard_error } = stability { + let diag = diagnostics::InternalOnlyCTargetFeature { feature: base_feature, enabled: if enable { "enabled" } else { "disabled" }, reason, @@ -528,9 +528,12 @@ pub(crate) fn provide(providers: &mut Providers) { (Stability::Stable, _) | ( Stability::Unstable { .. }, - Stability::Unstable { .. } | Stability::Forbidden { .. }, + Stability::Unstable { .. } | Stability::InternalOnly { .. }, ) - | (Stability::Forbidden { .. }, Stability::Forbidden { .. }) => { + | ( + Stability::InternalOnly { .. }, + Stability::InternalOnly { .. }, + ) => { // The stability in the entry is at least as good as the new // one, just keep it. } diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index ca0bba589f79a..7babc06050335 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -376,7 +376,7 @@ pub struct Session { pub asm_arch: Option, /// Set of actually enabled features for the current target, including ones that are not - /// in `cfg(target_feature)` because they are unstable or forbidden. + /// in `cfg(target_feature)` because they are unstable or internal-only. /// This is used by the compiler itself when it needs to know which target features are actually /// going to be enabled in the backend. pub internal_target_features: FxIndexSet, diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs index 9473a583c6586..f1dd2d8191985 100644 --- a/compiler/rustc_target/src/target_features.rs +++ b/compiler/rustc_target/src/target_features.rs @@ -28,8 +28,8 @@ //! call ABI. For example, disabling the `x87` feature on x86 changes how scalar floats are passed as //! arguments, so letting people toggle that feature would be unsound. To this end, the //! [`Target::abi_required_features`] function computes which target features must and must not be -//! enabled for any given target, and individual features can also be marked as [`Forbidden`]. See -//! for some more context. +//! enabled for any given target, and individual features can also be marked as [`InternalOnly`]. +//! See for some more context. //! //! The one exception to features that change the ABI is features that enable larger vector //! registers. Those are permitted to be listed here. The `*_FOR_CORRECT_VECTOR_ABI` arrays store @@ -70,17 +70,21 @@ pub enum Stability { /// feature gate! Symbol, ), + /// This is not actually something we expose as a "target feature" to our users. + /// We just manage it internally as a target feature since that's how LLVM represents it. /// This feature can not be set via `-Ctarget-feature` or `#[target_feature]`, it can only be /// set in the target spec. It is never set in `cfg(target_feature)`. Used in particular for /// features are actually ABI configuration flags (such as "soft-float" on many targets). - /// However, "forbidden" target features can still sometimes be enabled via `-Ctarget-cpu` or - /// target feature implications (on the Rust/LLVM level). To prevent that, ABI-relevant target - /// features are ideally pinned down (required or forbidden) in - /// [`Target::abi_required_features`]. - Forbidden { + /// + /// However, "internal" target features can still sometimes be enabled or disabled via + /// `-Ctarget-cpu` or Rust/LLVM target feature implications. Make sure nothing implies this + /// target feature and nothing is implied by this target feature (except for other internal-only + /// features). Ideally, ABI-relevant target features are pinned down (marked as required or + /// incompatible) in [`Target::abi_required_features`]. + InternalOnly { reason: &'static str, /// True if this is always an error, false if this can be reported as a warning when set via - /// `-Ctarget-feature`. + /// `-Ctarget-feature` (and a hard error when set via `#[target_feature]`). hard_error: bool, }, } @@ -121,7 +125,9 @@ impl Stability { } } Stability::Stable { .. } => None, - Stability::Forbidden { .. } => panic!("forbidden features should not reach this far"), + Stability::InternalOnly { .. } => { + panic!("internal-only features should not reach this far") + } } } @@ -139,7 +145,7 @@ impl Stability { Stability::Unstable(_) | Stability::CfgStableToggleUnstable(_) | Stability::Stable { .. } => Ok(()), - Stability::Forbidden { reason, hard_error: _ } => Err(reason), + Stability::InternalOnly { reason, hard_error: _ } => Err(reason), } } } @@ -158,7 +164,8 @@ static ARM_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("aes", Unstable(sym::arm_target_feature), &["neon"]), ( "atomics-32", - Stability::Forbidden { + // Not implied by any CPU model or other feature. + Stability::InternalOnly { reason: "unsound because it changes the ABI of atomic operations", hard_error: false, }, @@ -245,7 +252,8 @@ static AARCH64_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ // We forbid directly toggling just `fp-armv8`; it must be toggled with `neon`. ( "fp-armv8", - Stability::Forbidden { reason: "Rust ties `fp-armv8` to `neon`", hard_error: false }, + // Pinned down by [`Target::abi_required_features`] when needed. + Stability::InternalOnly { reason: "Rust ties `fp-armv8` to `neon`", hard_error: false }, &[], ), // FEAT_FP8 @@ -312,7 +320,8 @@ static AARCH64_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("rdm", Stable, &["neon"]), ( "reserve-x18", - Forbidden { reason: "use `-Zfixed-x18` compiler flag instead", hard_error: false }, + // Not implied by any CPU model or other feature; the compiler flag is a target modifier. + InternalOnly { reason: "use `-Zfixed-x18` compiler flag instead", hard_error: false }, &[], ), // FEAT_SB @@ -493,7 +502,8 @@ static X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("rdseed", Stable, &[]), ( "retpoline-external-thunk", - Stability::Forbidden { + // Not implied by any CPU model or other feature; the compiler flag is a target modifier. + Stability::InternalOnly { reason: "use `-Zretpoline-external-thunk` compiler flag instead", hard_error: false, }, @@ -501,7 +511,8 @@ static X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ), ( "retpoline-indirect-branches", - Stability::Forbidden { + // Not implied by any CPU model or other feature; the compiler flag is a target modifier. + Stability::InternalOnly { reason: "use `-Zretpoline` compiler flag instead", hard_error: false, }, @@ -509,7 +520,8 @@ static X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ), ( "retpoline-indirect-calls", - Stability::Forbidden { + // Not implied by any CPU model or other feature; the compiler flag is a target modifier. + Stability::InternalOnly { reason: "use `-Zretpoline` compiler flag instead", hard_error: false, }, @@ -522,7 +534,8 @@ static X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("sm4", Stable, &["avx2"]), ( "soft-float", - Stability::Forbidden { reason: "use a soft-float target instead", hard_error: false }, + // Pinned down by [`Target::abi_required_features`]. + Stability::InternalOnly { reason: "use a soft-float target instead", hard_error: false }, &[], ), ("sse", Stable, &[]), @@ -586,7 +599,8 @@ static POWERPC_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("altivec", Unstable(sym::powerpc_target_feature), &[]), ( "hard-float", - Forbidden { reason: "unsupported ABI-configuration feature", hard_error: false }, + // Pinned down by [`Target::abi_required_features`]. + InternalOnly { reason: "unsupported ABI-configuration feature", hard_error: false }, &[], ), ("msync", Unstable(sym::powerpc_target_feature), &[]), @@ -598,7 +612,12 @@ static POWERPC_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("power9-vector", Unstable(sym::powerpc_target_feature), &["power8-vector", "power9-altivec"]), ("power10-vector", Unstable(sym::powerpc_target_feature), &["power9-vector"]), ("quadword-atomics", Unstable(sym::powerpc_target_feature), &[]), - ("spe", Forbidden { reason: "unsupported ABI-configuration feature", hard_error: false }, &[]), + ( + "spe", + // Pinned down by [`Target::abi_required_features`]. + InternalOnly { reason: "unsupported ABI-configuration feature", hard_error: false }, + &[], + ), ("vsx", Unstable(sym::powerpc_target_feature), &["altivec"]), // tidy-alphabetical-end ]; @@ -662,7 +681,8 @@ static RISCV_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("f", CfgStableToggleUnstable(sym::riscv_target_feature), &["zicsr"]), ( "forced-atomics", - Stability::Forbidden { + // Not implied by any CPU model or other feature. + Stability::InternalOnly { reason: "unsound because it changes the ABI of atomic operations", hard_error: false, }, @@ -922,7 +942,8 @@ const IBMZ_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("miscellaneous-extensions-3", Stable, &[]), ("miscellaneous-extensions-4", Stable, &[]), ("nnp-assist", Stable, &["vector"]), - ("soft-float", Forbidden { reason: "unsupported ABI-configuration feature", hard_error: false }, &[]), + // Pinned down by [`Target::abi_required_features`]. + ("soft-float", InternalOnly { reason: "unsupported ABI-configuration feature", hard_error: false }, &[]), ("transactional-execution", Unstable(sym::s390x_target_feature), &[]), ("vector", Stable, &[]), ("vector-enhancements-1", Stable, &["vector"]), @@ -976,7 +997,8 @@ static AVR_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("spmx", Unstable(sym::avr_target_feature), &[]), ( "sram", - Forbidden { reason: "devices that have no SRAM are unsupported", hard_error: false }, + // Pinned down by [`Target::abi_required_features`]. + InternalOnly { reason: "devices that have no SRAM are unsupported", hard_error: false }, &[], ), ("tinyencoding", Unstable(sym::avr_target_feature), &[]), @@ -991,7 +1013,11 @@ const XTENSA_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ ("interrupt", Unstable(sym::xtensa_target_feature), &["exception"]), ( "windowed", - Forbidden { reason: "windowed changes the Xtensa calling convention", hard_error: false }, + // Pinned down by [`Target::abi_required_features`]. + InternalOnly { + reason: "windowed changes the Xtensa calling convention", + hard_error: false, + }, &["exception"], ), ("loop", Unstable(sym::xtensa_target_feature), &[]), @@ -1018,17 +1044,17 @@ const XTENSA_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[ /// IMPORTANT: If you're adding another feature list above, make sure to add it to this iterator! pub fn all_rust_features() -> impl Iterator { std::iter::empty() - .chain(ARM_FEATURES.iter()) - .chain(AARCH64_FEATURES.iter()) - .chain(X86_FEATURES.iter()) - .chain(HEXAGON_FEATURES.iter()) - .chain(POWERPC_FEATURES.iter()) - .chain(MIPS_FEATURES.iter()) - .chain(NVPTX_FEATURES.iter()) - .chain(RISCV_FEATURES.iter()) - .chain(WASM_FEATURES.iter()) - .chain(BPF_FEATURES.iter()) - .chain(XTENSA_FEATURES.iter()) + .chain(ARM_FEATURES) + .chain(AARCH64_FEATURES) + .chain(X86_FEATURES) + .chain(HEXAGON_FEATURES) + .chain(POWERPC_FEATURES) + .chain(MIPS_FEATURES) + .chain(NVPTX_FEATURES) + .chain(RISCV_FEATURES) + .chain(WASM_FEATURES) + .chain(BPF_FEATURES) + .chain(XTENSA_FEATURES) .chain(CSKY_FEATURES) .chain(LOONGARCH_FEATURES) .chain(IBMZ_FEATURES) @@ -1204,7 +1230,7 @@ impl Target { } } - // Note: the returned set includes `base_feature`. + /// Note: the returned set includes `base_feature`. #[track_caller] pub fn implied_target_features<'a>( &self, @@ -1240,7 +1266,7 @@ impl Target { const NOTHING: FeatureConstraints = FeatureConstraints { required: &[], incompatible: &[] }; // Some architectures don't have a clean explicit ABI designation; instead, the ABI is // defined by target features. When that is the case, those target features must be - // "forbidden" in the list above to ensure that there is a consistent answer to the + // "internal-only" in the list above to ensure that there is a consistent answer to the // questions "which ABI is used". match &self.arch { Arch::X86 => { @@ -1315,9 +1341,9 @@ impl Target { // LLVM will use float registers when `fp-armv8` is available, e.g. for // calls to built-ins. The only way to ensure a consistent softfloat ABI // on aarch64 is to never enable `fp-armv8`, so we enforce that. - // In Rust we tie `neon` and `fp-armv8` together, therefore `neon` is the - // feature we have to mark as incompatible. - FeatureConstraints { required: &[], incompatible: &["neon"] } + // In Rust we tie `neon` and `fp-armv8` together, therefore `neon` is also + // marked as incompatible. + FeatureConstraints { required: &[], incompatible: &["neon", "fp-armv8"] } } None => { // Everything else is assumed to use a hardfloat ABI. neon and fp-armv8 must be enabled. From e5004d09b0b8ba484ac59586b3124d44a52cf509 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 5 Aug 2026 14:13:01 +0200 Subject: [PATCH 077/100] ensure that we never toggle internal target features via the attribute --- compiler/rustc_codegen_ssa/src/target_features.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs index 7705bb72bd890..69487d2039c31 100644 --- a/compiler/rustc_codegen_ssa/src/target_features.rs +++ b/compiler/rustc_codegen_ssa/src/target_features.rs @@ -122,7 +122,17 @@ pub(crate) fn from_target_feature_attr( } else { TargetFeatureKind::Enabled }; - target_features.push(TargetFeature { name, kind }) + target_features.push(TargetFeature { name, kind }); + + if !rust_target_features + .get(name.as_str()) + .is_some_and(|s| s.toggle_allowed().is_ok()) + { + tcx.dcx().span_delayed_bug( + feature_span, + format!("internal-only feature {name} should not be toggled by `#[target_feature]`"), + ); + } } } } From b9ad974f2024d36d678830dae707efb180b418a1 Mon Sep 17 00:00:00 2001 From: KR-bluejay Date: Thu, 6 Aug 2026 15:21:36 +0000 Subject: [PATCH 078/100] Fix FutureDropPoll shim for by-move async closures --- .../src/shim/async_destructor_ctor.rs | 17 ++++++++++++++++- .../async-drop/async-drop-future-drop-poll.rs | 17 +++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/ui/async-await/async-drop/async-drop-future-drop-poll.rs diff --git a/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs b/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs index 1347fd21db057..00cfeeea815c1 100644 --- a/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs +++ b/compiler/rustc_mir_transform/src/shim/async_destructor_ctor.rs @@ -204,8 +204,23 @@ fn build_adrop_for_coroutine_shim<'tcx>( let ty::Coroutine(coroutine_def_id, impl_args) = impl_ty.kind() else { bug!("build_adrop_for_coroutine_shim not for coroutine impl type: ({:?})", shim); }; + let ty::Coroutine(_, id_args) = *tcx.type_of(*coroutine_def_id).skip_binder().kind() else { + bug!() + }; let source_info = SourceInfo::outermost(span); - let body = tcx.optimized_mir(*coroutine_def_id).future_drop_poll().unwrap(); + + // If the kind tys differ, we must use the by-move body + let def_id = if id_args.as_coroutine().kind_ty() == impl_args.as_coroutine().kind_ty() { + *coroutine_def_id + } else { + assert_eq!( + impl_args.as_coroutine().kind_ty().to_opt_closure_kind().unwrap(), + ty::ClosureKind::FnOnce + ); + + tcx.coroutine_by_move_body_def_id(*coroutine_def_id) + }; + let body = tcx.optimized_mir(def_id).future_drop_poll().unwrap(); let mut body: Body<'tcx> = EarlyBinder::bind(tcx, body.clone()).instantiate(tcx, impl_args).skip_norm_wip(); body.source.instance = ty::InstanceKind::Shim(shim); diff --git a/tests/ui/async-await/async-drop/async-drop-future-drop-poll.rs b/tests/ui/async-await/async-drop/async-drop-future-drop-poll.rs new file mode 100644 index 0000000000000..7886cedd3a10a --- /dev/null +++ b/tests/ui/async-await/async-drop/async-drop-future-drop-poll.rs @@ -0,0 +1,17 @@ +// Regression test for #142559 +//@ build-pass +//@ compile-flags: --crate-type=lib +#![feature(async_drop)] +#![allow(incomplete_features)] + +//@ edition: 2024 + +async fn run(f: impl Fn() -> F) { + f().await; +} + +pub async fn async_drop_async_closure() { + let x = async || async {}.await; + + run(x).await; +} From 08e7eaf9629fb8d325fea229b165e6cc6d173b4e Mon Sep 17 00:00:00 2001 From: SomeFlyingThing <306498559+SomeFlyingThing@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:05:10 +0000 Subject: [PATCH 079/100] Remove fragile memchr codegen test --- .../lib-optimizations/memchr-result.rs | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 tests/codegen-llvm/lib-optimizations/memchr-result.rs diff --git a/tests/codegen-llvm/lib-optimizations/memchr-result.rs b/tests/codegen-llvm/lib-optimizations/memchr-result.rs deleted file mode 100644 index beeab470c08af..0000000000000 --- a/tests/codegen-llvm/lib-optimizations/memchr-result.rs +++ /dev/null @@ -1,38 +0,0 @@ -// Ensure `memchr` communicates that a returned index is in bounds. -//@ compile-flags: -Copt-level=3 -Zinline-mir=false -//@ only-x86_64 -//@ revisions: llvm-old llvm-new -//@ [llvm-old] max-llvm-major-version: 21 -//@ [llvm-new] min-llvm-version: 22 - -#![crate_type = "lib"] -#![feature(slice_internals)] - -extern crate core; - -use core::slice::memchr::{memchr, memrchr}; - -// CHECK-LABEL: @find_char -#[no_mangle] -pub fn find_char(haystack: &str, needle: char) -> Option { - // llvm-old: call void @llvm.assume - // llvm-new-NOT: phi { i64, i64 } - // CHECK: ret { i64, i64 } - haystack.find(needle) -} - -// CHECK-LABEL: @find_byte -#[no_mangle] -pub fn find_byte(haystack: &[u8], needle: u8) -> Option { - // llvm-new-NOT: panic_bounds_check - // CHECK: ret { i1, i8 } - memchr(needle, haystack).map(|index| haystack[index]) -} - -// CHECK-LABEL: @rfind_byte -#[no_mangle] -pub fn rfind_byte(haystack: &[u8], needle: u8) -> Option { - // CHECK-NOT: panic_bounds_check - // CHECK: ret { i1, i8 } - memrchr(needle, haystack).map(|index| haystack[index]) -} From f98cf4cefe471fc4ad246172b2a4aeaf91d1496f Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 6 Aug 2026 19:04:54 +0200 Subject: [PATCH 080/100] move naked function ui tests --- .../ffi.rs} | 0 .../ffi.stderr} | 2 +- .../inline.rs} | 0 .../inline.stderr} | 8 +++---- .../instruction-set.rs} | 0 .../invalid-attr.rs} | 0 .../invalid-attr.stderr} | 16 +++++++------- .../invalid-repr-attr.rs} | 0 .../invalid-repr-attr.stderr} | 12 +++++----- .../mono-sym-fn.rs} | 0 .../{ => naked-functions}/naked-functions.rs | 0 .../naked-functions.stderr | 0 .../rustic-abi.rs} | 0 .../shim.rs} | 0 .../target-feature.rs} | 0 .../testattrs.rs} | 0 .../testattrs.stderr} | 8 +++---- .../unused.aarch64.stderr} | 0 .../unused.rs} | 0 .../unused.x86_64.stderr} | 22 +++++++++---------- 20 files changed, 34 insertions(+), 34 deletions(-) rename tests/ui/asm/{naked-functions-ffi.rs => naked-functions/ffi.rs} (100%) rename tests/ui/asm/{naked-functions-ffi.stderr => naked-functions/ffi.stderr} (90%) rename tests/ui/asm/{naked-functions-inline.rs => naked-functions/inline.rs} (100%) rename tests/ui/asm/{naked-functions-inline.stderr => naked-functions/inline.stderr} (87%) rename tests/ui/asm/{naked-functions-instruction-set.rs => naked-functions/instruction-set.rs} (100%) rename tests/ui/asm/{naked-invalid-attr.rs => naked-functions/invalid-attr.rs} (100%) rename tests/ui/asm/{naked-invalid-attr.stderr => naked-functions/invalid-attr.stderr} (84%) rename tests/ui/asm/{naked-with-invalid-repr-attr.rs => naked-functions/invalid-repr-attr.rs} (100%) rename tests/ui/asm/{naked-with-invalid-repr-attr.stderr => naked-functions/invalid-repr-attr.stderr} (79%) rename tests/ui/asm/{naked-asm-mono-sym-fn.rs => naked-functions/mono-sym-fn.rs} (100%) rename tests/ui/asm/{ => naked-functions}/naked-functions.rs (100%) rename tests/ui/asm/{ => naked-functions}/naked-functions.stderr (100%) rename tests/ui/asm/{naked-functions-rustic-abi.rs => naked-functions/rustic-abi.rs} (100%) rename tests/ui/asm/{naked-function-shim.rs => naked-functions/shim.rs} (100%) rename tests/ui/asm/{naked-functions-target-feature.rs => naked-functions/target-feature.rs} (100%) rename tests/ui/asm/{naked-functions-testattrs.rs => naked-functions/testattrs.rs} (100%) rename tests/ui/asm/{naked-functions-testattrs.stderr => naked-functions/testattrs.stderr} (85%) rename tests/ui/asm/{naked-functions-unused.aarch64.stderr => naked-functions/unused.aarch64.stderr} (100%) rename tests/ui/asm/{naked-functions-unused.rs => naked-functions/unused.rs} (100%) rename tests/ui/asm/{naked-functions-unused.x86_64.stderr => naked-functions/unused.x86_64.stderr} (83%) diff --git a/tests/ui/asm/naked-functions-ffi.rs b/tests/ui/asm/naked-functions/ffi.rs similarity index 100% rename from tests/ui/asm/naked-functions-ffi.rs rename to tests/ui/asm/naked-functions/ffi.rs diff --git a/tests/ui/asm/naked-functions-ffi.stderr b/tests/ui/asm/naked-functions/ffi.stderr similarity index 90% rename from tests/ui/asm/naked-functions-ffi.stderr rename to tests/ui/asm/naked-functions/ffi.stderr index f7893a3b8de98..63c0f263e45e0 100644 --- a/tests/ui/asm/naked-functions-ffi.stderr +++ b/tests/ui/asm/naked-functions/ffi.stderr @@ -1,5 +1,5 @@ warning: `extern` fn uses type `char`, which is not FFI-safe - --> $DIR/naked-functions-ffi.rs:8:28 + --> $DIR/ffi.rs:8:28 | LL | pub extern "C" fn naked(p: char) -> u128 { | ^^^^ not FFI-safe diff --git a/tests/ui/asm/naked-functions-inline.rs b/tests/ui/asm/naked-functions/inline.rs similarity index 100% rename from tests/ui/asm/naked-functions-inline.rs rename to tests/ui/asm/naked-functions/inline.rs diff --git a/tests/ui/asm/naked-functions-inline.stderr b/tests/ui/asm/naked-functions/inline.stderr similarity index 87% rename from tests/ui/asm/naked-functions-inline.stderr rename to tests/ui/asm/naked-functions/inline.stderr index 68648be72328e..fa44f92040b4a 100644 --- a/tests/ui/asm/naked-functions-inline.stderr +++ b/tests/ui/asm/naked-functions/inline.stderr @@ -1,5 +1,5 @@ error[E0736]: attribute incompatible with `#[unsafe(naked)]` - --> $DIR/naked-functions-inline.rs:13:3 + --> $DIR/inline.rs:13:3 | LL | #[unsafe(naked)] | ---------------- function marked with `#[unsafe(naked)]` here @@ -7,7 +7,7 @@ LL | #[inline] | ^^^^^^ the `inline` attribute is incompatible with `#[unsafe(naked)]` error[E0736]: attribute incompatible with `#[unsafe(naked)]` - --> $DIR/naked-functions-inline.rs:20:3 + --> $DIR/inline.rs:20:3 | LL | #[unsafe(naked)] | ---------------- function marked with `#[unsafe(naked)]` here @@ -15,7 +15,7 @@ LL | #[inline(always)] | ^^^^^^ the `inline` attribute is incompatible with `#[unsafe(naked)]` error[E0736]: attribute incompatible with `#[unsafe(naked)]` - --> $DIR/naked-functions-inline.rs:27:3 + --> $DIR/inline.rs:27:3 | LL | #[unsafe(naked)] | ---------------- function marked with `#[unsafe(naked)]` here @@ -23,7 +23,7 @@ LL | #[inline(never)] | ^^^^^^ the `inline` attribute is incompatible with `#[unsafe(naked)]` error[E0736]: attribute incompatible with `#[unsafe(naked)]` - --> $DIR/naked-functions-inline.rs:34:18 + --> $DIR/inline.rs:34:18 | LL | #[unsafe(naked)] | ---------------- function marked with `#[unsafe(naked)]` here diff --git a/tests/ui/asm/naked-functions-instruction-set.rs b/tests/ui/asm/naked-functions/instruction-set.rs similarity index 100% rename from tests/ui/asm/naked-functions-instruction-set.rs rename to tests/ui/asm/naked-functions/instruction-set.rs diff --git a/tests/ui/asm/naked-invalid-attr.rs b/tests/ui/asm/naked-functions/invalid-attr.rs similarity index 100% rename from tests/ui/asm/naked-invalid-attr.rs rename to tests/ui/asm/naked-functions/invalid-attr.rs diff --git a/tests/ui/asm/naked-invalid-attr.stderr b/tests/ui/asm/naked-functions/invalid-attr.stderr similarity index 84% rename from tests/ui/asm/naked-invalid-attr.stderr rename to tests/ui/asm/naked-functions/invalid-attr.stderr index 0b55dbe0dbcf0..aa1d2fd07d38f 100644 --- a/tests/ui/asm/naked-invalid-attr.stderr +++ b/tests/ui/asm/naked-functions/invalid-attr.stderr @@ -1,11 +1,11 @@ error[E0433]: cannot find module or crate `a` in the crate root - --> $DIR/naked-invalid-attr.rs:57:5 + --> $DIR/invalid-attr.rs:57:5 | LL | #[::a] | ^ use of unresolved module or unlinked crate `a` error: the `naked` attribute cannot be used on crates - --> $DIR/naked-invalid-attr.rs:5:11 + --> $DIR/invalid-attr.rs:5:11 | LL | #![unsafe(naked)] | ^^^^^ @@ -13,7 +13,7 @@ LL | #![unsafe(naked)] = help: the `naked` attribute can only be applied to functions error: the `naked` attribute cannot be used on foreign functions - --> $DIR/naked-invalid-attr.rs:10:14 + --> $DIR/invalid-attr.rs:10:14 | LL | #[unsafe(naked)] | ^^^^^ @@ -21,7 +21,7 @@ LL | #[unsafe(naked)] = help: the `naked` attribute can only be applied to functions with a body error: the `naked` attribute cannot be used on structs - --> $DIR/naked-invalid-attr.rs:14:10 + --> $DIR/invalid-attr.rs:14:10 | LL | #[unsafe(naked)] | ^^^^^ @@ -29,7 +29,7 @@ LL | #[unsafe(naked)] = help: the `naked` attribute can only be applied to functions error: the `naked` attribute cannot be used on struct fields - --> $DIR/naked-invalid-attr.rs:17:14 + --> $DIR/invalid-attr.rs:17:14 | LL | #[unsafe(naked)] | ^^^^^ @@ -37,7 +37,7 @@ LL | #[unsafe(naked)] = help: the `naked` attribute can only be applied to functions error: the `naked` attribute cannot be used on required trait methods - --> $DIR/naked-invalid-attr.rs:23:14 + --> $DIR/invalid-attr.rs:23:14 | LL | #[unsafe(naked)] | ^^^^^ @@ -45,7 +45,7 @@ LL | #[unsafe(naked)] = help: the `naked` attribute can only be applied to functions with a body error: the `naked` attribute cannot be used on closures - --> $DIR/naked-invalid-attr.rs:52:14 + --> $DIR/invalid-attr.rs:52:14 | LL | #[unsafe(naked)] | ^^^^^ @@ -53,7 +53,7 @@ LL | #[unsafe(naked)] = help: the `naked` attribute can be applied to functions and methods error[E0736]: attribute incompatible with `#[unsafe(naked)]` - --> $DIR/naked-invalid-attr.rs:57:3 + --> $DIR/invalid-attr.rs:57:3 | LL | #[::a] | ^^^ the `::a` attribute is incompatible with `#[unsafe(naked)]` diff --git a/tests/ui/asm/naked-with-invalid-repr-attr.rs b/tests/ui/asm/naked-functions/invalid-repr-attr.rs similarity index 100% rename from tests/ui/asm/naked-with-invalid-repr-attr.rs rename to tests/ui/asm/naked-functions/invalid-repr-attr.rs diff --git a/tests/ui/asm/naked-with-invalid-repr-attr.stderr b/tests/ui/asm/naked-functions/invalid-repr-attr.stderr similarity index 79% rename from tests/ui/asm/naked-with-invalid-repr-attr.stderr rename to tests/ui/asm/naked-functions/invalid-repr-attr.stderr index 7f12510b8aad6..5b9482e3297c5 100644 --- a/tests/ui/asm/naked-with-invalid-repr-attr.stderr +++ b/tests/ui/asm/naked-functions/invalid-repr-attr.stderr @@ -1,5 +1,5 @@ error: the `repr(C)` attribute cannot be used on functions - --> $DIR/naked-with-invalid-repr-attr.rs:10:3 + --> $DIR/invalid-repr-attr.rs:10:3 | LL | #[repr(C)] | ^^^^^^^ @@ -7,7 +7,7 @@ LL | #[repr(C)] = help: the `repr(C)` attribute can only be applied to data types error: the `repr(transparent)` attribute cannot be used on functions - --> $DIR/naked-with-invalid-repr-attr.rs:17:3 + --> $DIR/invalid-repr-attr.rs:17:3 | LL | #[repr(transparent)] | ^^^^^^^^^^^^^^^^^ @@ -15,7 +15,7 @@ LL | #[repr(transparent)] = help: the `repr(transparent)` attribute can only be applied to data types error: the `repr(C)` attribute cannot be used on functions - --> $DIR/naked-with-invalid-repr-attr.rs:24:3 + --> $DIR/invalid-repr-attr.rs:24:3 | LL | #[repr(C)] | ^^^^^^^ @@ -23,7 +23,7 @@ LL | #[repr(C)] = help: the `repr(C)` attribute can only be applied to data types error: the `repr(C)` attribute cannot be used on functions - --> $DIR/naked-with-invalid-repr-attr.rs:33:3 + --> $DIR/invalid-repr-attr.rs:33:3 | LL | #[repr(C, packed)] | ^^^^^^^^^^^^^^^ @@ -31,7 +31,7 @@ LL | #[repr(C, packed)] = help: the `repr(C)` attribute can only be applied to data types error: the `repr(packed)` attribute cannot be used on functions - --> $DIR/naked-with-invalid-repr-attr.rs:33:3 + --> $DIR/invalid-repr-attr.rs:33:3 | LL | #[repr(C, packed)] | ^^^^^^^^^^^^^^^ @@ -39,7 +39,7 @@ LL | #[repr(C, packed)] = help: the `repr(packed)` attribute can only be applied to data types error: the `repr(u8)` attribute cannot be used on functions - --> $DIR/naked-with-invalid-repr-attr.rs:41:3 + --> $DIR/invalid-repr-attr.rs:41:3 | LL | #[repr(u8)] | ^^^^^^^^ diff --git a/tests/ui/asm/naked-asm-mono-sym-fn.rs b/tests/ui/asm/naked-functions/mono-sym-fn.rs similarity index 100% rename from tests/ui/asm/naked-asm-mono-sym-fn.rs rename to tests/ui/asm/naked-functions/mono-sym-fn.rs diff --git a/tests/ui/asm/naked-functions.rs b/tests/ui/asm/naked-functions/naked-functions.rs similarity index 100% rename from tests/ui/asm/naked-functions.rs rename to tests/ui/asm/naked-functions/naked-functions.rs diff --git a/tests/ui/asm/naked-functions.stderr b/tests/ui/asm/naked-functions/naked-functions.stderr similarity index 100% rename from tests/ui/asm/naked-functions.stderr rename to tests/ui/asm/naked-functions/naked-functions.stderr diff --git a/tests/ui/asm/naked-functions-rustic-abi.rs b/tests/ui/asm/naked-functions/rustic-abi.rs similarity index 100% rename from tests/ui/asm/naked-functions-rustic-abi.rs rename to tests/ui/asm/naked-functions/rustic-abi.rs diff --git a/tests/ui/asm/naked-function-shim.rs b/tests/ui/asm/naked-functions/shim.rs similarity index 100% rename from tests/ui/asm/naked-function-shim.rs rename to tests/ui/asm/naked-functions/shim.rs diff --git a/tests/ui/asm/naked-functions-target-feature.rs b/tests/ui/asm/naked-functions/target-feature.rs similarity index 100% rename from tests/ui/asm/naked-functions-target-feature.rs rename to tests/ui/asm/naked-functions/target-feature.rs diff --git a/tests/ui/asm/naked-functions-testattrs.rs b/tests/ui/asm/naked-functions/testattrs.rs similarity index 100% rename from tests/ui/asm/naked-functions-testattrs.rs rename to tests/ui/asm/naked-functions/testattrs.rs diff --git a/tests/ui/asm/naked-functions-testattrs.stderr b/tests/ui/asm/naked-functions/testattrs.stderr similarity index 85% rename from tests/ui/asm/naked-functions-testattrs.stderr rename to tests/ui/asm/naked-functions/testattrs.stderr index ad2041ec118b9..5039a202efb74 100644 --- a/tests/ui/asm/naked-functions-testattrs.stderr +++ b/tests/ui/asm/naked-functions/testattrs.stderr @@ -1,5 +1,5 @@ error[E0736]: cannot use `#[unsafe(naked)]` with testing attributes - --> $DIR/naked-functions-testattrs.rs:11:1 + --> $DIR/testattrs.rs:11:1 | LL | #[test] | ------- function marked with testing attribute here @@ -7,7 +7,7 @@ LL | #[unsafe(naked)] | ^^^^^^^^^^^^^^^^ `#[unsafe(naked)]` is incompatible with testing attributes error[E0736]: cannot use `#[unsafe(naked)]` with testing attributes - --> $DIR/naked-functions-testattrs.rs:19:1 + --> $DIR/testattrs.rs:19:1 | LL | #[test] | ------- function marked with testing attribute here @@ -15,7 +15,7 @@ LL | #[unsafe(naked)] | ^^^^^^^^^^^^^^^^ `#[unsafe(naked)]` is incompatible with testing attributes error[E0736]: cannot use `#[unsafe(naked)]` with testing attributes - --> $DIR/naked-functions-testattrs.rs:27:1 + --> $DIR/testattrs.rs:27:1 | LL | #[test] | ------- function marked with testing attribute here @@ -23,7 +23,7 @@ LL | #[unsafe(naked)] | ^^^^^^^^^^^^^^^^ `#[unsafe(naked)]` is incompatible with testing attributes error[E0736]: cannot use `#[unsafe(naked)]` with testing attributes - --> $DIR/naked-functions-testattrs.rs:34:1 + --> $DIR/testattrs.rs:34:1 | LL | #[bench] | -------- function marked with testing attribute here diff --git a/tests/ui/asm/naked-functions-unused.aarch64.stderr b/tests/ui/asm/naked-functions/unused.aarch64.stderr similarity index 100% rename from tests/ui/asm/naked-functions-unused.aarch64.stderr rename to tests/ui/asm/naked-functions/unused.aarch64.stderr diff --git a/tests/ui/asm/naked-functions-unused.rs b/tests/ui/asm/naked-functions/unused.rs similarity index 100% rename from tests/ui/asm/naked-functions-unused.rs rename to tests/ui/asm/naked-functions/unused.rs diff --git a/tests/ui/asm/naked-functions-unused.x86_64.stderr b/tests/ui/asm/naked-functions/unused.x86_64.stderr similarity index 83% rename from tests/ui/asm/naked-functions-unused.x86_64.stderr rename to tests/ui/asm/naked-functions/unused.x86_64.stderr index bfb2923b0b8d6..a41e80fdc50d6 100644 --- a/tests/ui/asm/naked-functions-unused.x86_64.stderr +++ b/tests/ui/asm/naked-functions/unused.x86_64.stderr @@ -1,66 +1,66 @@ error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:16:32 + --> $DIR/unused.rs:16:32 | LL | pub extern "C" fn function(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: the lint level is defined here - --> $DIR/naked-functions-unused.rs:5:9 + --> $DIR/unused.rs:5:9 | LL | #![deny(unused)] | ^^^^^^ = note: `#[deny(unused_variables)]` implied by `#[deny(unused)]` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:16:42 + --> $DIR/unused.rs:16:42 | LL | pub extern "C" fn function(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:27:38 + --> $DIR/unused.rs:27:38 | LL | pub extern "C" fn associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:27:48 + --> $DIR/unused.rs:27:48 | LL | pub extern "C" fn associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:35:41 + --> $DIR/unused.rs:35:41 | LL | pub extern "C" fn method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:35:51 + --> $DIR/unused.rs:35:51 | LL | pub extern "C" fn method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:45:40 + --> $DIR/unused.rs:45:40 | LL | extern "C" fn trait_associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:45:50 + --> $DIR/unused.rs:45:50 | LL | extern "C" fn trait_associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:53:43 + --> $DIR/unused.rs:53:43 | LL | extern "C" fn trait_method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:53:53 + --> $DIR/unused.rs:53:53 | LL | extern "C" fn trait_method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` From 2c388dec8ab766dd6bd6c62183c3a6be29abc197 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 6 Aug 2026 23:26:12 +0200 Subject: [PATCH 081/100] miri: implement more restrictive trivial-ABI checks --- .../rustc_const_eval/src/interpret/call.rs | 93 +++++++++++++++---- .../abi_mismatch_zst_array.rs | 8 ++ .../abi_mismatch_zst_array.stderr | 20 ++++ .../abi_mismatch_zst_repr_C.rs | 11 +++ .../abi_mismatch_zst_repr_C.stderr | 20 ++++ .../abi_mismatch_zst_transparent_array.rs | 11 +++ .../abi_mismatch_zst_transparent_array.stderr | 20 ++++ .../tests/pass/function_calls/abi_compat.rs | 10 +- 8 files changed, 171 insertions(+), 22 deletions(-) create mode 100644 src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_array.rs create mode 100644 src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_array.stderr create mode 100644 src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_repr_C.rs create mode 100644 src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_repr_C.stderr create mode 100644 src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_transparent_array.rs create mode 100644 src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_transparent_array.stderr diff --git a/compiler/rustc_const_eval/src/interpret/call.rs b/compiler/rustc_const_eval/src/interpret/call.rs index bf3cce6e55624..2528f4f00b5d9 100644 --- a/compiler/rustc_const_eval/src/interpret/call.rs +++ b/compiler/rustc_const_eval/src/interpret/call.rs @@ -70,27 +70,81 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { }) } - /// Find the wrapped inner type of a transparent wrapper. - /// Must not be called on 1-ZST (as they don't have a uniquely defined "wrapped field"). + /// Returns whether the given type has trivial ABI. + fn has_trivial_abi(&self, layout: TyAndLayout<'tcx>) -> InterpResult<'tcx, bool> { + if !layout.is_1zst() { + return interp_ok(false); + } + match *layout.ty.kind() { + // Trivally trivial-ABI types (because Rust makes no promises about their ABI). + ty::Tuple(..) + | ty::Never + | ty::FnDef(..) + | ty::Closure(..) + | ty::Coroutine(..) + | ty::CoroutineClosure(..) => interp_ok(true), + + ty::Array(elem, _len) => { + // 0-length arrays are in general *not* okay, but arrays of trivial-ABI types are. + self.has_trivial_abi(self.layout_of(elem)?) + } + ty::Adt(adt_def, _args) => { + if adt_def.repr().transparent() { + // All fields must have trivial ABI. + (0..layout.fields.count()).try_fold(true, |acc, idx| { + interp_ok(acc && self.has_trivial_abi(layout.field(self, idx))?) + }) + } else if adt_def.repr().c() { + interp_ok(false) + } else { + // Must be repr(Rust). + interp_ok(true) + } + } + + ty::Alias(..) => panic!("non-normalized type"), + _ => interp_ok(false), + } + } + + /// Find the wrapped inner type of a transparent wrapper by going for the unique + /// non-trivial-ABI field. /// /// We work with `TyAndLayout` here since that makes it much easier to iterate over all fields. fn unfold_transparent( &self, layout: TyAndLayout<'tcx>, may_unfold: impl Fn(AdtDef<'tcx>) -> bool, - ) -> TyAndLayout<'tcx> { + ) -> InterpResult<'tcx, TyAndLayout<'tcx>> { match layout.ty.kind() { ty::Adt(adt_def, _) if adt_def.repr().transparent() && may_unfold(*adt_def) => { assert_matches!(layout.variants, rustc_abi::Variants::Single { .. }); - // Find the non-1-ZST field, and recurse. - let (_, field) = layout.non_1zst_field(self).unwrap(); + // Look for non-trivial-ABI field(s). + let mut found = None; + for idx in 0..layout.fields.count() { + let field = layout.field(self, idx); + if self.has_trivial_abi(field)? { + continue; + } + // Found a non-trivial ABI field! + if found.is_some() { + // There is more than one such field. + // FIXME: we should just panic here. But currently such repr(transparent) + // types are still accepted. We just don't treat them as transparent. + return interp_ok(layout); + } + found = Some(field); + } + let Some(field) = found else { + // All fields have trivial ABI. That means this type is effectively `()`. + return interp_ok(self.layout_of(self.tcx.types.unit)?); + }; + // Recurse. self.unfold_transparent(field, may_unfold) } - ty::Pat(base, _) => self.layout_of(*base).expect( - "if the layout of a pattern type could be computed, so can the layout of its base", - ), + ty::Pat(base, _) => interp_ok(self.layout_of(*base)?), // Not a transparent type, no further unfolding. - _ => layout, + _ => interp_ok(layout), } } @@ -145,7 +199,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let inner = self.unfold_transparent(inner, /* may_unfold */ |def| { // Stop at NPO types so that we don't miss that attribute in the check below! def.is_struct() && !is_npo(def) - }); + })?; interp_ok(match inner.ty.kind() { ty::Ref(..) | ty::FnPtr(..) => { // Option<&T> behaves like &T, and same for fn() @@ -154,7 +208,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ty::Adt(def, _) if is_npo(*def) => { // Once we found a `nonnull_optimization_guaranteed` type, further strip off // newtype structs from it to find the underlying ABI type. - self.unfold_transparent(inner, /* may_unfold */ |def| def.is_struct()) + self.unfold_transparent(inner, /* may_unfold */ |def| def.is_struct())? } _ => { // Everything else we do not unfold. @@ -175,16 +229,21 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { if caller.ty == callee.ty { return interp_ok(true); } - // 1-ZST are compatible with all 1-ZST (and with nothing else). - if caller.is_1zst() || callee.is_1zst() { - return interp_ok(caller.is_1zst() && callee.is_1zst()); + // Handle trivial-ABI types. + if self.has_trivial_abi(caller)? && self.has_trivial_abi(callee)? { + return interp_ok(true); } // Unfold newtypes and NPO optimizations. let unfold = |layout: TyAndLayout<'tcx>| { - self.unfold_npo(self.unfold_transparent(layout, /* may_unfold */ |_def| true)) + self.unfold_transparent(layout, /* may_unfold */ |_def| true) + .and_then(|f| self.unfold_npo(f)) }; let caller = unfold(caller)?; let callee = unfold(callee)?; + // Not-quite-so-fast path: if the types are equal now, they are compatible. + if caller.ty == callee.ty { + return interp_ok(true); + } // Now see if these inner types are compatible. // Compatible pointer types. For thin pointers, we have to accept even non-`repr(transparent)` @@ -240,8 +299,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { return interp_ok(caller == callee); } - // Fall back to exact equality. - interp_ok(caller == callee) + // The rest is incompatible. + interp_ok(false) } /// Returns a `bool` saying whether the two arguments are ABI-compatible. diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_array.rs b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_array.rs new file mode 100644 index 0000000000000..c9e4badcac464 --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_array.rs @@ -0,0 +1,8 @@ +fn callee(_s: [u8; 0]) {} +//~^ ERROR: type [u8; 0] passing argument of type () + +fn main() { + let fnptr: fn([u8; 0]) = callee; + let fnptr: fn(()) = unsafe { std::mem::transmute(fnptr) }; + fnptr(()); +} diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_array.stderr b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_array.stderr new file mode 100644 index 0000000000000..90f6b87d7635d --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_array.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: calling a function whose parameter #1 has type [u8; 0] passing argument of type () + --> tests/fail/function_pointers/abi_mismatch_zst_array.rs:LL:CC + | +LL | fn callee(_s: [u8; 0]) {} + | ^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets + = help: if you think this code should be accepted anyway, please report an issue with Miri + = note: stack backtrace: + 0: callee + at tests/fail/function_pointers/abi_mismatch_zst_array.rs:LL:CC + 1: main + at tests/fail/function_pointers/abi_mismatch_zst_array.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_repr_C.rs b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_repr_C.rs new file mode 100644 index 0000000000000..71911266ebf1f --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_repr_C.rs @@ -0,0 +1,11 @@ +#[repr(C)] +struct C; + +fn callee() {} +//~^ ERROR: return type () passing return place of type C + +fn main() { + let fnptr: fn() -> () = callee; + let fnptr: fn() -> C = unsafe { std::mem::transmute(fnptr) }; + fnptr(); +} diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_repr_C.stderr b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_repr_C.stderr new file mode 100644 index 0000000000000..bb2f8fc78a29d --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_repr_C.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: calling a function with return type () passing return place of type C + --> tests/fail/function_pointers/abi_mismatch_zst_repr_C.rs:LL:CC + | +LL | fn callee() {} + | ^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets + = help: if you think this code should be accepted anyway, please report an issue with Miri + = note: stack backtrace: + 0: callee + at tests/fail/function_pointers/abi_mismatch_zst_repr_C.rs:LL:CC + 1: main + at tests/fail/function_pointers/abi_mismatch_zst_repr_C.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_transparent_array.rs b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_transparent_array.rs new file mode 100644 index 0000000000000..63608981b1e29 --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_transparent_array.rs @@ -0,0 +1,11 @@ +#[repr(transparent)] +struct Wrap([u8; 0]); + +fn callee(_s: Wrap) {} +//~^ ERROR: type Wrap passing argument of type () + +fn main() { + let fnptr: fn(Wrap) = callee; + let fnptr: fn(()) = unsafe { std::mem::transmute(fnptr) }; + fnptr(()); +} diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_transparent_array.stderr b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_transparent_array.stderr new file mode 100644 index 0000000000000..5d5e03349fd8e --- /dev/null +++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_zst_transparent_array.stderr @@ -0,0 +1,20 @@ +error: Undefined Behavior: calling a function whose parameter #1 has type Wrap passing argument of type () + --> tests/fail/function_pointers/abi_mismatch_zst_transparent_array.rs:LL:CC + | +LL | fn callee(_s: Wrap) {} + | ^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets + = help: if you think this code should be accepted anyway, please report an issue with Miri + = note: stack backtrace: + 0: callee + at tests/fail/function_pointers/abi_mismatch_zst_transparent_array.rs:LL:CC + 1: main + at tests/fail/function_pointers/abi_mismatch_zst_transparent_array.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/pass/function_calls/abi_compat.rs b/src/tools/miri/tests/pass/function_calls/abi_compat.rs index 94cb5695fac50..56c45eb29a0bf 100644 --- a/src/tools/miri/tests/pass/function_calls/abi_compat.rs +++ b/src/tools/miri/tests/pass/function_calls/abi_compat.rs @@ -62,11 +62,11 @@ fn test_abi_newtype() { struct Wrapper2a((), T); #[repr(transparent)] #[derive(Copy, Clone)] - struct Wrapper3(Zst, T, [u8; 0]); + struct Wrapper3(Zst, T, [(); 0]); #[repr(transparent)] #[derive(Copy, Clone)] enum Wrapper4 { - V(Zst, T, [u8; 0]), + V(Zst, T, [(); 10]), } let t = T::default(); @@ -74,7 +74,7 @@ fn test_abi_newtype() { test_abi_compat(t, Wrapper2(t, ())); test_abi_compat(t, Wrapper2a((), t)); test_abi_compat(t, Wrapper3(Zst, t, [])); - test_abi_compat(t, Wrapper4::V(Zst, t, [])); + test_abi_compat(t, Wrapper4::V(Zst, t, [(); _])); // MaybeUninit is `repr(transparent)`; that covers the `union` case. test_abi_compat(t, mem::MaybeUninit::new(t)); } @@ -100,8 +100,8 @@ fn main() { test_abi_compat(&0u32, &([true; 4], [0u32; 0])); // - `fn` types test_abi_compat(main as fn(), id:: as fn(i32) -> i32); - // - 1-ZST - test_abi_compat((), [0u8; 0]); + // - trivial-ABI types + test_abi_compat((), [(); 0]); // Guaranteed null-pointer-layout optimizations: // - Guaranteed Option null-pointer-optimizations (RFC 3391). From b5c330c30eab0c85cf47892b6cf3d43c921bcdbf Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Thu, 6 Aug 2026 23:38:00 +0200 Subject: [PATCH 082/100] snippet emitter: rework debug impls --- .../src/annotate_snippet_emitter_writer.rs | 35 +++++++++++------- compiler/rustc_span/src/source_map.rs | 36 ++++++++++++++++++- 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs index c3c9f26c31571..7e7f72943c7cd 100644 --- a/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs +++ b/compiler/rustc_errors/src/annotate_snippet_emitter_writer.rs @@ -42,7 +42,6 @@ pub struct AnnotateSnippetEmitter { ui_testing: bool, ignored_directories_in_source_blocks: Vec, diagnostic_width: Option, - macro_backtrace: bool, track_diagnostics: bool, terminal_url: TerminalUrl, @@ -51,18 +50,30 @@ pub struct AnnotateSnippetEmitter { impl Debug for AnnotateSnippetEmitter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let AnnotateSnippetEmitter { + dst, + sm, + short_message, + ui_testing, + ignored_directories_in_source_blocks, + diagnostic_width, + macro_backtrace, + track_diagnostics, + terminal_url, + theme, + } = self; + f.debug_struct("AnnotateSnippetEmitter") - .field("short_message", &self.short_message) - .field("ui_testing", &self.ui_testing) - .field( - "ignored_directories_in_source_blocks", - &self.ignored_directories_in_source_blocks, - ) - .field("diagnostic_width", &self.diagnostic_width) - .field("macro_backtrace", &self.macro_backtrace) - .field("track_diagnostics", &self.track_diagnostics) - .field("terminal_url", &self.terminal_url) - .field("theme", &self.theme) + .field("dst", &format_args!("")) + .field("sm", sm) + .field("short_message", short_message) + .field("ui_testing", ui_testing) + .field("ignored_directories_in_source_blocks", ignored_directories_in_source_blocks) + .field("diagnostic_width", diagnostic_width) + .field("macro_backtrace", macro_backtrace) + .field("track_diagnostics", track_diagnostics) + .field("terminal_url", terminal_url) + .field("theme", theme) .finish() } } diff --git a/compiler/rustc_span/src/source_map.rs b/compiler/rustc_span/src/source_map.rs index 47c933e245d49..80d1bae71ae89 100644 --- a/compiler/rustc_span/src/source_map.rs +++ b/compiler/rustc_span/src/source_map.rs @@ -174,6 +174,18 @@ struct SourceMapFiles { stable_id_to_source_file: UnhashMap>, } +impl std::fmt::Debug for SourceMapFiles { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let SourceMapFiles { source_files, stable_id_to_source_file: _ } = self; + + f.debug_list() + .entries( + source_files.iter().map(|f| f.name.prefer_remapped_unconditionally().to_string()), + ) + .finish() + } +} + /// Used to construct a `SourceMap` with `SourceMap::with_inputs`. pub struct SourceMapInputs { pub file_loader: Box, @@ -203,6 +215,28 @@ pub struct SourceMap { checksum_hash_kind: Option, } +impl std::fmt::Debug for SourceMap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let SourceMap { + files, + file_loader, + path_mapping, + working_dir, + hash_kind, + checksum_hash_kind, + } = self; + + f.debug_struct("SourceMap") + .field("files", files) + .field("file_loader", &format_args!("")) + .field("path_mapping", path_mapping) + .field("working_dir", working_dir) + .field("hash_kind", hash_kind) + .field("checksum_hash_kind", checksum_hash_kind) + .finish() + } +} + impl SourceMap { pub fn new(path_mapping: FilePathMapping) -> SourceMap { Self::with_inputs(SourceMapInputs { @@ -1117,7 +1151,7 @@ pub fn get_source_map() -> Option> { with_session_globals(|session_globals| session_globals.source_map.clone()) } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct FilePathMapping { mapping: Vec<(PathBuf, PathBuf)>, filename_remapping_scopes: RemapPathScopeComponents, From b829f17aa86deac34b7cc0b5b55c553c94e5d6d6 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 5 Aug 2026 11:01:52 +1000 Subject: [PATCH 083/100] Simplify `MaybeTransitiveLiveLocals` It's mostly identical to `MaybeLiveLocals`, and we can delegate most of its operations to `MaybeLiveLocals`. Note that there was a tiny difference between `MaybeLiveLocals::apply_call_return_effect` and `MaybeTransitiveLiveLocals::apply_call_return_effect`: the former uses `state.kill(local)`, the latter used `state.remove(local)`. The two are equivalent so the difference didn't matter, but it does demonstrate the dangers of the code duplication. --- .../rustc_mir_dataflow/src/impls/liveness.rs | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs index da2ea948366db..f23ef674ba6dc 100644 --- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs @@ -208,7 +208,8 @@ impl DefUse { } } -/// Like `MaybeLiveLocals`, but does not mark locals as live if they are used in a dead assignment. +/// Like `MaybeLiveLocals` (and layered on top of `MaybeLiveLocals`), but does not mark locals as +/// live if they are used in a dead assignment. /// /// This is basically written for dead store elimination and nothing else. /// @@ -274,12 +275,11 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> { const NAME: &'static str = "transitive liveness"; fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain { - // bottom = not live - DenseBitSet::new_empty(body.local_decls.len()) + MaybeLiveLocals.bottom_value(body) } - fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) { - // No variables are live until we observe a use + fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) { + MaybeLiveLocals.initialize_start_block(body, state) } fn apply_primary_statement_effect( @@ -288,6 +288,7 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> { statement: &mir::Statement<'tcx>, location: Location, ) { + // This is the one part of `MaybeTransitiveLiveLocals` that differs from `MaybeLiveLocals`. if let Some(destination) = Self::can_be_removed_if_dead(&statement.kind, &self.always_live, &self.debuginfo_locals) && !state.contains(destination.local) @@ -295,7 +296,8 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> { // This store is dead return; } - TransferFunction(state).visit_statement(statement, location); + + MaybeLiveLocals.apply_primary_statement_effect(state, statement, location); } fn apply_primary_terminator_effect( @@ -304,27 +306,15 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> { terminator: &mir::Terminator<'tcx>, location: Location, ) { - TransferFunction(state).visit_terminator(terminator, location); + MaybeLiveLocals.apply_primary_terminator_effect(state, terminator, location) } fn apply_call_return_effect( &self, state: &mut Self::Domain, - _block: mir::BasicBlock, + block: mir::BasicBlock, return_places: CallReturnPlaces<'_, 'tcx>, ) { - if let CallReturnPlaces::Yield(resume_place) = return_places { - YieldResumeEffect(state).visit_place( - &resume_place, - PlaceContext::MutatingUse(MutatingUseContext::Yield), - Location::START, - ) - } else { - return_places.for_each(|place| { - if let Some(local) = place.as_local() { - state.remove(local); - } - }); - } + MaybeLiveLocals.apply_call_return_effect(state, block, return_places); } } From 530c82cb96e19e367edb4c3661e762aaedfa3979 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 5 Aug 2026 13:18:30 +1000 Subject: [PATCH 084/100] Rename `TransferFunction` As `LivenessTransferFunction`. This avoids renaming it via a `use` item, which makes things clearer. --- compiler/rustc_mir_dataflow/src/impls/liveness.rs | 12 ++++++------ compiler/rustc_mir_dataflow/src/impls/mod.rs | 3 +-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs index f23ef674ba6dc..dafde78e91ee7 100644 --- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs @@ -24,8 +24,8 @@ use crate::{Analysis, Backward, GenKill}; pub struct MaybeLiveLocals; impl MaybeLiveLocals { - pub fn transfer_function(state: &mut I) -> TransferFunction<'_, I> { - TransferFunction(state) + pub fn transfer_function(state: &mut I) -> LivenessTransferFunction<'_, I> { + LivenessTransferFunction(state) } } @@ -50,7 +50,7 @@ impl<'tcx> Analysis<'tcx> for MaybeLiveLocals { statement: &mir::Statement<'tcx>, location: Location, ) { - TransferFunction(state).visit_statement(statement, location); + LivenessTransferFunction(state).visit_statement(statement, location); } fn apply_primary_terminator_effect( @@ -59,7 +59,7 @@ impl<'tcx> Analysis<'tcx> for MaybeLiveLocals { terminator: &mir::Terminator<'tcx>, location: Location, ) { - TransferFunction(state).visit_terminator(terminator, location); + LivenessTransferFunction(state).visit_terminator(terminator, location); } fn apply_call_return_effect( @@ -84,9 +84,9 @@ impl<'tcx> Analysis<'tcx> for MaybeLiveLocals { } } -pub struct TransferFunction<'a, I>(pub &'a mut I); +pub struct LivenessTransferFunction<'a, I>(pub &'a mut I); -impl<'tcx, I> Visitor<'tcx> for TransferFunction<'_, I> +impl<'tcx, I> Visitor<'tcx> for LivenessTransferFunction<'_, I> where I: GenKill, { diff --git a/compiler/rustc_mir_dataflow/src/impls/mod.rs b/compiler/rustc_mir_dataflow/src/impls/mod.rs index 6d573e1c00e1c..1e12e41ce1fb4 100644 --- a/compiler/rustc_mir_dataflow/src/impls/mod.rs +++ b/compiler/rustc_mir_dataflow/src/impls/mod.rs @@ -9,8 +9,7 @@ pub use self::initialized::{ MaybeUninitializedPlaces, MaybeUninitializedPlacesDomain, }; pub use self::liveness::{ - DefUse, MaybeLiveLocals, MaybeTransitiveLiveLocals, - TransferFunction as LivenessTransferFunction, + DefUse, LivenessTransferFunction, MaybeLiveLocals, MaybeTransitiveLiveLocals, }; pub use self::storage_liveness::{ MaybeRequiresStorage, MaybeStorageDead, MaybeStorageLive, always_storage_live_locals, From 7e95965ac1853b29b7e0aaf7858c363eb52895b8 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 5 Aug 2026 13:19:15 +1000 Subject: [PATCH 085/100] Remove `MaybeLiveLocals::transfer_function` It has only two uses, and it's just a synonym for `LivenessTransferFunction`, which has more uses. --- compiler/rustc_mir_dataflow/src/impls/liveness.rs | 6 ------ compiler/rustc_mir_transform/src/dest_prop.rs | 6 +++--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs index dafde78e91ee7..a82fed864400f 100644 --- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs @@ -23,12 +23,6 @@ use crate::{Analysis, Backward, GenKill}; /// [liveness]: https://en.wikipedia.org/wiki/Live_variable_analysis pub struct MaybeLiveLocals; -impl MaybeLiveLocals { - pub fn transfer_function(state: &mut I) -> LivenessTransferFunction<'_, I> { - LivenessTransferFunction(state) - } -} - impl<'tcx> Analysis<'tcx> for MaybeLiveLocals { type Domain = DenseBitSet; type Direction = Backward; diff --git a/compiler/rustc_mir_transform/src/dest_prop.rs b/compiler/rustc_mir_transform/src/dest_prop.rs index e392f856696be..924125404a07a 100644 --- a/compiler/rustc_mir_transform/src/dest_prop.rs +++ b/compiler/rustc_mir_transform/src/dest_prop.rs @@ -144,7 +144,7 @@ use rustc_index::{IndexVec, newtype_index}; use rustc_middle::mir::visit::{MutVisitor, PlaceContext, VisitPlacesWith, Visitor}; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; -use rustc_mir_dataflow::impls::{DefUse, MaybeLiveLocals}; +use rustc_mir_dataflow::impls::{DefUse, LivenessTransferFunction, MaybeLiveLocals}; use rustc_mir_dataflow::points::DenseLocationMap; use rustc_mir_dataflow::{Analysis, EntryStates, GenKill}; use tracing::{debug, trace}; @@ -619,7 +619,7 @@ fn save_as_intervals<'tcx>( state.current = state.current + 1; debug_assert_eq!(state.current, two_step_loc(loc, Effect::Before)); - MaybeLiveLocals::transfer_function(&mut state).visit_terminator(term, loc); + LivenessTransferFunction(&mut state).visit_terminator(term, loc); for (statement_index, stmt) in block_data.statements.iter().enumerate().rev() { let loc = Location { block, statement_index }; @@ -659,7 +659,7 @@ fn save_as_intervals<'tcx>( // the all the writes we manually marked as live in the second half of the statement. state.current = TwoStepIndex::from_u32(state.current.as_u32() + 1); debug_assert_eq!(state.current, two_step_loc(loc, Effect::Before)); - MaybeLiveLocals::transfer_function(&mut state).visit_statement(stmt, loc); + LivenessTransferFunction(&mut state).visit_statement(stmt, loc); } // Cleanup the current block for the next one. From 0ae7d22265d64302c4bde60b34ad927a5525b6a1 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 5 Aug 2026 13:23:05 +1000 Subject: [PATCH 086/100] Remove an unnecessary lifetime --- compiler/rustc_mir_dataflow/src/impls/liveness.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs index a82fed864400f..673d170c84789 100644 --- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs @@ -228,7 +228,7 @@ impl<'a> MaybeTransitiveLiveLocals<'a> { pub fn can_be_removed_if_dead<'tcx>( stmt_kind: &StatementKind<'tcx>, always_live: &DenseBitSet, - debuginfo_locals: &'a DenseBitSet, + debuginfo_locals: &DenseBitSet, ) -> Option> { // Compute the place that we are storing to, if any let destination = match stmt_kind { From d1689e22cf97f0e9414f770ae5ec6ba50bdbbbce Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 5 Aug 2026 13:23:29 +1000 Subject: [PATCH 087/100] Fix a typo --- compiler/rustc_mir_dataflow/src/impls/liveness.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs index 673d170c84789..ff373e906683a 100644 --- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs @@ -214,7 +214,7 @@ pub struct MaybeTransitiveLiveLocals<'a> { } impl<'a> MaybeTransitiveLiveLocals<'a> { - /// The `always_alive` set is the set of locals to which all stores should unconditionally be + /// The `always_live` set is the set of locals to which all stores should unconditionally be /// considered live. /// /// This should include at least all locals that are ever borrowed. From d7a07f0345486d4f576eab8fb435ba6be5caafbf Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 5 Aug 2026 13:27:23 +1000 Subject: [PATCH 088/100] Remove unused derives on `DefUse` --- compiler/rustc_mir_dataflow/src/impls/liveness.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs index ff373e906683a..3208f876af11c 100644 --- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs @@ -130,7 +130,6 @@ impl<'tcx> Visitor<'tcx> for YieldResumeEffect<'_> { } } -#[derive(Eq, PartialEq, Clone)] pub enum DefUse { /// Full write to the local. Def, From a7b542a64b43011f14ea58ce8e5c761b0f4af7c9 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 5 Aug 2026 13:54:22 +1000 Subject: [PATCH 089/100] Remove unnecessary `&` sigils --- compiler/rustc_mir_dataflow/src/impls/liveness.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs index 3208f876af11c..5c77827a67165 100644 --- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs @@ -283,7 +283,7 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> { ) { // This is the one part of `MaybeTransitiveLiveLocals` that differs from `MaybeLiveLocals`. if let Some(destination) = - Self::can_be_removed_if_dead(&statement.kind, &self.always_live, &self.debuginfo_locals) + Self::can_be_removed_if_dead(&statement.kind, self.always_live, self.debuginfo_locals) && !state.contains(destination.local) { // This store is dead From 162cba5202c77d583a9cfb1de3b6b3c0d5cf9fbb Mon Sep 17 00:00:00 2001 From: sgasho Date: Thu, 6 Aug 2026 23:16:59 +0000 Subject: [PATCH 090/100] dlopen Offload --- compiler/rustc_codegen_llvm/src/back/write.rs | 28 ++-- .../src/builder/gpu_offload.rs | 2 +- .../rustc_codegen_llvm/src/diagnostics.rs | 13 ++ compiler/rustc_codegen_llvm/src/lib.rs | 20 +++ compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 57 -------- compiler/rustc_codegen_llvm/src/llvm/mod.rs | 2 + .../src/llvm/offload_ffi.rs | 133 ++++++++++++++++++ .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 105 -------------- .../llvm-wrapper/offload/CMakeLists.txt | 27 ++++ .../llvm-wrapper/offload/OffloadWrapper.cpp | 117 +++++++++++++++ src/bootstrap/src/core/build_steps/compile.rs | 8 ++ src/bootstrap/src/core/build_steps/llvm.rs | 92 +++++++++++- src/bootstrap/src/core/builder/mod.rs | 1 + src/bootstrap/src/lib.rs | 6 +- 14 files changed, 435 insertions(+), 176 deletions(-) create mode 100644 compiler/rustc_codegen_llvm/src/llvm/offload_ffi.rs create mode 100644 compiler/rustc_llvm/llvm-wrapper/offload/CMakeLists.txt create mode 100644 compiler/rustc_llvm/llvm-wrapper/offload/OffloadWrapper.cpp diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index edf52e67b434b..6aaefbe82ec2b 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -720,7 +720,11 @@ pub(crate) unsafe fn llvm_optimize( // Here we map the old arguments to the new arguments, with an offset of 1 to make sure // that we don't use the newly added `%dyn_ptr`. unsafe { - llvm::LLVMRustOffloadMapper(old_fn, new_fn, old_args_rebuilt.as_ptr()); + llvm::RustOffloadWrapper::get_instance().llvm_rust_offload_wrapper( + old_fn, + new_fn, + old_args_rebuilt.as_slice(), + ); } llvm::set_linkage(new_fn, llvm::get_linkage(old_fn)); @@ -814,16 +818,16 @@ pub(crate) unsafe fn llvm_optimize( let device_dir = device_path.parent().unwrap(); let device_out = device_dir.join("device.bin"); let device_out_c = path_to_c_string(device_out.as_path()); - unsafe { - // 1) Bundle device module into offload image device.bin (device TM) - let ok = llvm::LLVMRustBundleImages( + // 1) Bundle device module into offload image device.bin (device TM) + let ok = unsafe { + llvm::RustOffloadWrapper::get_instance().llvm_rust_bundle_images( module.module_llvm.llmod(), module.module_llvm.tm.raw(), - device_out_c.as_ptr(), - ); - if !ok || !device_out.exists() { - dcx.emit_err(crate::diagnostics::OffloadBundleImagesFailed); - } + device_out_c.as_c_str(), + ) + }; + if !ok || !device_out.exists() { + dcx.emit_err(crate::diagnostics::OffloadBundleImagesFailed); } } @@ -859,8 +863,10 @@ pub(crate) unsafe fn llvm_optimize( // We create a full clone of our LLVM host module, since we will embed the device IR // into it, and this might break caching or incremental compilation otherwise. let llmod2 = llvm::LLVMCloneModule(module.module_llvm.llmod()); - let ok = - unsafe { llvm::LLVMRustOffloadEmbedBufferInModule(llmod2, device_bin_c.as_ptr()) }; + let ok = unsafe { + llvm::RustOffloadWrapper::get_instance() + .llvm_rust_offload_embed_buffer_in_module(llmod2, device_bin_c.as_c_str()) + }; if !ok { dcx.emit_err(crate::diagnostics::OffloadEmbedFailed); } diff --git a/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs b/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs index 0b009321802cf..3d0bb6fcc48fd 100644 --- a/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs +++ b/compiler/rustc_codegen_llvm/src/builder/gpu_offload.rs @@ -296,7 +296,7 @@ struct KernelArgsTy { impl KernelArgsTy { const OFFLOAD_VERSION: u64 = 3; - const FLAGS: u64 = 0; + const FLAGS: u64 = 1 << 6; // Enable StrictBlocksAndThreads const TRIPCOUNT: u64 = 0; fn new_decl<'ll>(cx: &CodegenCx<'ll, '_>) -> &'ll Type { let kernel_arguments_ty = cx.type_named_struct("struct.__tgt_kernel_arguments"); diff --git a/compiler/rustc_codegen_llvm/src/diagnostics.rs b/compiler/rustc_codegen_llvm/src/diagnostics.rs index ea29683b9d289..54f8ffbb881da 100644 --- a/compiler/rustc_codegen_llvm/src/diagnostics.rs +++ b/compiler/rustc_codegen_llvm/src/diagnostics.rs @@ -60,6 +60,19 @@ pub(crate) struct AutoDiffWithoutLto; #[diag("using the autodiff feature requires -Z autodiff=Enable")] pub(crate) struct AutoDiffWithoutEnable; +#[derive(Diagnostic)] +#[diag("failed to load our rust offload backend: {$err}")] +pub(crate) struct RustOffloadComponentUnavailable { + pub err: String, +} + +#[derive(Diagnostic)] +#[diag("rust offload backend not found in the sysroot: {$err}")] +#[note("it will be distributed via rustup in the future")] +pub(crate) struct RustOffloadComponentMissing { + pub err: String, +} + #[derive(Diagnostic)] #[diag( "using the offload feature requires -Z offload=" diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index 3ec0495956c4c..fe39fc6b3fca0 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -373,6 +373,26 @@ impl CodegenBackend for LlvmCodegenBackend { } fn codegen_crate<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Box { + use rustc_session::config::Offload; + + if tcx.sess.opts.unstable_opts.offload.contains(&Offload::Device) + || tcx.sess.opts.unstable_opts.offload.iter().any(|o| matches!(o, Offload::Host(_))) + { + match llvm::RustOffloadWrapper::get_or_init(&tcx.sess.opts.sysroot) { + Ok(_) => {} + Err(llvm::RustOffloadLibraryError::NotFound { err }) => { + tcx.sess + .dcx() + .emit_fatal(crate::diagnostics::RustOffloadComponentMissing { err }); + } + Err(llvm::RustOffloadLibraryError::LoadFailed { err }) => { + tcx.sess + .dcx() + .emit_fatal(crate::diagnostics::RustOffloadComponentUnavailable { err }); + } + } + } + Box::new(rustc_codegen_ssa::base::codegen_crate(LlvmCodegenBackend(()), tcx)) } diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 4cc5d326bdc9e..1a60b59a93525 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -1713,63 +1713,6 @@ unsafe extern "C" { ) -> &'a Value; } -#[cfg(feature = "llvm_offload")] -pub(crate) use self::Offload::*; - -#[cfg(feature = "llvm_offload")] -mod Offload { - use super::*; - unsafe extern "C" { - /// Processes the module and writes it in an offload compatible way into a "device.bin" file. - pub(crate) fn LLVMRustBundleImages<'a>( - M: &'a Module, - TM: &'a TargetMachine, - device_bin: *const c_char, - ) -> bool; - pub(crate) unsafe fn LLVMRustOffloadEmbedBufferInModule<'a>( - _M: &'a Module, - _device_bin: *const c_char, - ) -> bool; - pub(crate) fn LLVMRustOffloadMapper<'a>( - OldFn: &'a Value, - NewFn: &'a Value, - RebuiltArgs: *const &Value, - ); - } -} - -#[cfg(not(feature = "llvm_offload"))] -pub(crate) use self::Offload_fallback::*; - -#[cfg(not(feature = "llvm_offload"))] -mod Offload_fallback { - use super::*; - /// Processes the module and writes it in an offload compatible way into a "device.bin" file. - /// Marked as unsafe to match the real offload wrapper which is unsafe due to FFI. - #[allow(unused_unsafe)] - pub(crate) unsafe fn LLVMRustBundleImages<'a>( - _M: &'a Module, - _TM: &'a TargetMachine, - _device_bin: *const c_char, - ) -> bool { - unimplemented!("This rustc version was not built with LLVM Offload support!"); - } - pub(crate) unsafe fn LLVMRustOffloadEmbedBufferInModule<'a>( - _M: &'a Module, - _device_bin: *const c_char, - ) -> bool { - unimplemented!("This rustc version was not built with LLVM Offload support!"); - } - #[allow(unused_unsafe)] - pub(crate) unsafe fn LLVMRustOffloadMapper<'a>( - _OldFn: &'a Value, - _NewFn: &'a Value, - _RebuiltArgs: *const &Value, - ) { - unimplemented!("This rustc version was not built with LLVM Offload support!"); - } -} - // FFI bindings for `DIBuilder` functions in the LLVM-C API. // Try to keep these in the same order as in `llvm/include/llvm-c/DebugInfo.h`. // diff --git a/compiler/rustc_codegen_llvm/src/llvm/mod.rs b/compiler/rustc_codegen_llvm/src/llvm/mod.rs index a2d17e93b4996..eb7a529c0b198 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/mod.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/mod.rs @@ -21,8 +21,10 @@ pub(crate) mod diagnostic; pub(crate) mod enzyme_ffi; mod ffi; mod metadata_kind; +pub(crate) mod offload_ffi; pub(crate) use self::enzyme_ffi::*; +pub(crate) use self::offload_ffi::*; impl LLVMRustResult { pub(crate) fn into_result(self) -> Result<(), ()> { diff --git a/compiler/rustc_codegen_llvm/src/llvm/offload_ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/offload_ffi.rs new file mode 100644 index 0000000000000..46d9320248a9b --- /dev/null +++ b/compiler/rustc_codegen_llvm/src/llvm/offload_ffi.rs @@ -0,0 +1,133 @@ +use std::ffi::{CStr, c_char}; +use std::sync::OnceLock; + +use super::ffi::{Module, TargetMachine, Value}; + +type LLVMRustBundleImagesFn = unsafe extern "C" fn(&Module, &TargetMachine, *const c_char) -> bool; +type LLVMRustOffloadEmbedBufferInModuleFn = unsafe extern "C" fn(&Module, *const c_char) -> bool; +type LLVMRustOffloadMapperFn = unsafe extern "C" fn(&Value, &Value, *const &Value); + +use rustc_session::config::host_tuple; +use rustc_session::filesearch; + +use crate::llvm::LLVMRustVersionMajor; + +pub(crate) struct RustOffloadWrapper { + LLVMRustBundleImages: LLVMRustBundleImagesFn, + LLVMRustOffloadEmbedBufferInModule: LLVMRustOffloadEmbedBufferInModuleFn, + LLVMRustOffloadMapper: LLVMRustOffloadMapperFn, + // Keep the dynamic library loaded while the function pointers are used. + _lib: libloading::Library, +} + +#[derive(Debug)] +pub(crate) enum RustOffloadLibraryError { + NotFound { err: String }, + LoadFailed { err: String }, +} + +impl From for RustOffloadLibraryError { + fn from(err: libloading::Error) -> Self { + Self::LoadFailed { err: format!("{err:?}") } + } +} + +static OFFLOAD_INSTANCE: OnceLock = OnceLock::new(); + +impl RustOffloadWrapper { + pub(crate) fn get_or_init( + sysroot: &rustc_session::config::Sysroot, + ) -> Result<&'static RustOffloadWrapper, RustOffloadLibraryError> { + OFFLOAD_INSTANCE.get_or_try_init(|| { + let w = Self::call_dynamic(sysroot)?; + Ok(w) + }) + } + + pub(crate) fn get_instance() -> &'static RustOffloadWrapper { + OFFLOAD_INSTANCE + .get() + .expect("RustOffloadWrapper not initialized. Call get_or_init with sysroot first.") + } + + pub(crate) unsafe fn llvm_rust_bundle_images( + &self, + m: &Module, + tm: &TargetMachine, + c: &CStr, + ) -> bool { + unsafe { (self.LLVMRustBundleImages)(m, tm, c.as_ptr()) } + } + + pub(crate) unsafe fn llvm_rust_offload_embed_buffer_in_module( + &self, + m: &Module, + i: &CStr, + ) -> bool { + unsafe { (self.LLVMRustOffloadEmbedBufferInModule)(m, i.as_ptr()) } + } + + pub(crate) unsafe fn llvm_rust_offload_wrapper(&self, v1: &Value, v2: &Value, vs: &[&Value]) { + unsafe { (self.LLVMRustOffloadMapper)(v1, v2, vs.as_ptr()) } + } + + fn call_dynamic( + sysroot: &rustc_session::config::Sysroot, + ) -> Result { + let rust_offload_path = Self::get_rust_offload_path(sysroot)?; + let lib = unsafe { libloading::Library::new(rust_offload_path)? }; + + let llvm_rust_bundle_images = + *unsafe { lib.get::(b"LLVMRustBundleImages\0")? }; + let llvm_rust_offload_embed_buffer_in_module = *unsafe { + lib.get::( + b"LLVMRustOffloadEmbedBufferInModule\0", + )? + }; + let llvm_rust_offload_wrapper = + *unsafe { lib.get::(b"LLVMRustOffloadMapper\0")? }; + + Ok(Self { + LLVMRustBundleImages: llvm_rust_bundle_images, + LLVMRustOffloadEmbedBufferInModule: llvm_rust_offload_embed_buffer_in_module, + LLVMRustOffloadMapper: llvm_rust_offload_wrapper, + _lib: lib, + }) + } + + fn get_rust_offload_path( + sysroot: &rustc_session::config::Sysroot, + ) -> Result { + let llvm_version_major = unsafe { LLVMRustVersionMajor() }; + + let path_buf = sysroot + .all_paths() + .find_map(|p| { + let candidate = filesearch::make_target_lib_path(p, host_tuple()) + .join(format!("libRustOffload-{}", llvm_version_major)) + .with_extension(std::env::consts::DLL_EXTENSION); + + candidate.exists().then_some(candidate) + }) + .ok_or_else(|| { + let candidates = sysroot + .all_paths() + .map(|p| p.join("lib").display().to_string()) + .collect::>() + .join("\n* "); + RustOffloadLibraryError::NotFound { + err: format!( + "failed to find a `libRustOffload-{llvm_version_major}` \ + in the sysroot candidates:\n* {candidates}" + ), + } + })?; + + Ok(path_buf + .to_str() + .ok_or_else(|| RustOffloadLibraryError::LoadFailed { + err: format!("invalid UTF-8 in path: {}", path_buf.display()), + })? + .to_string()) + } +} diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index f500041a12d8b..983a506bd4ac6 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -164,111 +164,6 @@ extern "C" bool LLVMRustIsCall(LLVMValueRef V) { return llvm::isa(llvm::unwrap(V)); } -// Some of the functions here rely on LLVM modules that may not always be -// available. As such, we only try to build it in the first place, if -// llvm.offload is enabled. -#ifdef OFFLOAD -static Error writeFile(StringRef Filename, StringRef Data) { - Expected> OutputOrErr = - FileOutputBuffer::create(Filename, Data.size()); - if (!OutputOrErr) - return OutputOrErr.takeError(); - std::unique_ptr Output = std::move(*OutputOrErr); - llvm::copy(Data, Output->getBufferStart()); - if (Error E = Output->commit()) - return E; - return Error::success(); -} - -// This is the first of many steps in creating a binary using llvm offload, -// to run code on the gpu. Concrete, it replaces the following binary use: -// clang-offload-packager -o device.bin -// --image=file=device.bc,triple=amdgcn-amd-amdhsa,arch=gfx90a,kind=openmp -// The input module is the rust code compiled for a gpu target like amdgpu. -// Based on clang/tools/clang-offload-packager/ClangOffloadPackager.cpp -extern "C" bool LLVMRustBundleImages(LLVMModuleRef M, TargetMachine &TM, - const char *HostOutPath) { - std::string Storage; - llvm::raw_string_ostream OS1(Storage); - llvm::WriteBitcodeToFile(*unwrap(M), OS1); - OS1.flush(); - auto MB = llvm::MemoryBuffer::getMemBufferCopy(Storage, "device.bc"); - - SmallVector BinaryData; - raw_svector_ostream OS2(BinaryData); - - OffloadBinary::OffloadingImage ImageBinary{}; - ImageBinary.TheImageKind = object::IMG_Bitcode; - ImageBinary.Image = std::move(MB); - ImageBinary.TheOffloadKind = object::OFK_OpenMP; - - std::string TripleStr = TM.getTargetTriple().str(); - llvm::StringRef CPURef = TM.getTargetCPU(); - ImageBinary.StringData["triple"] = TripleStr; - ImageBinary.StringData["arch"] = CPURef; - llvm::SmallString<0> Buffer = OffloadBinary::write(ImageBinary); - if (Buffer.size() % OffloadBinary::getAlignment() != 0) - // Offload binary has invalid size alignment - return false; - OS2 << Buffer; - if (Error E = writeFile(HostOutPath, - StringRef(BinaryData.begin(), BinaryData.size()))) - return false; - return true; -} - -extern "C" bool LLVMRustOffloadEmbedBufferInModule(LLVMModuleRef HostM, - const char *HostOutPath) { - auto MBOrErr = MemoryBuffer::getFile(HostOutPath); - if (!MBOrErr) { - auto E = MBOrErr.getError(); - auto _B = errorCodeToError(E); - return false; - } - MemoryBufferRef Buf = (*MBOrErr)->getMemBufferRef(); - Module *M = unwrap(HostM); - StringRef SectionName = ".llvm.offloading"; - Align Alignment = Align(8); - llvm::embedBufferInModule(*M, Buf, SectionName, Alignment); - return true; -} - -// Clone OldFn into NewFn, remapping its arguments to RebuiltArgs. -// Each arg of OldFn is replaced with the corresponding value in RebuiltArgs. -// For scalars, RebuiltArgs contains the value cast and/or truncated to the -// original type. -extern "C" void LLVMRustOffloadMapper(LLVMValueRef OldFn, LLVMValueRef NewFn, - const LLVMValueRef *RebuiltArgs) { - llvm::Function *oldFn = llvm::unwrap(OldFn); - llvm::Function *newFn = llvm::unwrap(NewFn); - - // Map old arguments to new arguments. We skip the first dyn_ptr argument, - // since it can't be used directly by user code. - llvm::ValueToValueMapTy vmap; - auto newArgIt = newFn->arg_begin(); - newArgIt->setName("dyn_ptr"); - - unsigned i = 0; - for (auto &oldArg : oldFn->args()) { - vmap[&oldArg] = unwrap(RebuiltArgs[i++]); - } - - llvm::SmallVector returns; - llvm::CloneFunctionInto(newFn, oldFn, vmap, - llvm::CloneFunctionChangeType::LocalChangesOnly, - returns); - - BasicBlock &entry = newFn->getEntryBlock(); - BasicBlock &clonedEntry = *std::next(newFn->begin()); - - if (entry.getTerminator()) - entry.getTerminator()->eraseFromParent(); - - IRBuilder<> B(&entry); - B.CreateBr(&clonedEntry); -} -#endif - extern "C" LLVMValueRef LLVMRustGetNamedValue(LLVMModuleRef M, const char *Name, size_t NameLen) { return wrap(unwrap(M)->getNamedValue(StringRef(Name, NameLen))); diff --git a/compiler/rustc_llvm/llvm-wrapper/offload/CMakeLists.txt b/compiler/rustc_llvm/llvm-wrapper/offload/CMakeLists.txt new file mode 100644 index 0000000000000..37c747a902d87 --- /dev/null +++ b/compiler/rustc_llvm/llvm-wrapper/offload/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.20) +project(RustOffload LANGUAGES CXX) + +find_package(LLVM CONFIG REQUIRED) + +add_library(RustOffload-${LLVM_VERSION_MAJOR} SHARED + OffloadWrapper.cpp +) + +target_include_directories(RustOffload-${LLVM_VERSION_MAJOR} PRIVATE + ${LLVM_INCLUDE_DIRS} +) + +target_link_libraries(RustOffload-${LLVM_VERSION_MAJOR} PRIVATE + LLVM +) + +if(NOT LLVM_ENABLE_RTTI) + target_compile_options( + RustOffload-${LLVM_VERSION_MAJOR} + PRIVATE -fno-rtti + ) +endif() + +install(TARGETS RustOffload-${LLVM_VERSION_MAJOR} + LIBRARY DESTINATION lib +) diff --git a/compiler/rustc_llvm/llvm-wrapper/offload/OffloadWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/offload/OffloadWrapper.cpp new file mode 100644 index 0000000000000..8c18f2453e9d8 --- /dev/null +++ b/compiler/rustc_llvm/llvm-wrapper/offload/OffloadWrapper.cpp @@ -0,0 +1,117 @@ +#include "../SuppressLLVMWarnings.h" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Bitcode/BitcodeWriter.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/Object/OffloadBinary.h" +#include "llvm/Support/CBindingWrapping.h" +#include "llvm/Support/FileOutputBuffer.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Target/TargetMachine.h" +#include "llvm/Transforms/Utils/Cloning.h" +#include "llvm/Transforms/Utils/ModuleUtils.h" +#include "llvm/Transforms/Utils/ValueMapper.h" + +using namespace llvm; +using namespace llvm::object; + +static Error writeFile(StringRef Filename, StringRef Data) { + Expected> OutputOrErr = + FileOutputBuffer::create(Filename, Data.size()); + if (!OutputOrErr) + return OutputOrErr.takeError(); + std::unique_ptr Output = std::move(*OutputOrErr); + llvm::copy(Data, Output->getBufferStart()); + if (Error E = Output->commit()) + return E; + return Error::success(); +} + +// This is the first of many steps in creating a binary using llvm offload, +// to run code on the gpu. Concrete, it replaces the following binary use: +// clang-offload-packager -o device.bin +// --image=file=device.bc,triple=amdgcn-amd-amdhsa,arch=gfx90a,kind=openmp +// The input module is the rust code compiled for a gpu target like amdgpu. +// Based on clang/tools/clang-offload-packager/ClangOffloadPackager.cpp +extern "C" bool LLVMRustBundleImages(LLVMModuleRef M, TargetMachine &TM, + const char *HostOutPath) { + std::string Storage; + llvm::raw_string_ostream OS1(Storage); + llvm::WriteBitcodeToFile(*unwrap(M), OS1); + OS1.flush(); + auto MB = llvm::MemoryBuffer::getMemBufferCopy(Storage, "device.bc"); + + SmallVector BinaryData; + raw_svector_ostream OS2(BinaryData); + + OffloadBinary::OffloadingImage ImageBinary{}; + ImageBinary.TheImageKind = object::IMG_Bitcode; + ImageBinary.Image = std::move(MB); + ImageBinary.TheOffloadKind = object::OFK_OpenMP; + + std::string TripleStr = TM.getTargetTriple().str(); + llvm::StringRef CPURef = TM.getTargetCPU(); + ImageBinary.StringData["triple"] = TripleStr; + ImageBinary.StringData["arch"] = CPURef; + llvm::SmallString<0> Buffer = OffloadBinary::write(ImageBinary); + if (Buffer.size() % OffloadBinary::getAlignment() != 0) + // Offload binary has invalid size alignment + return false; + OS2 << Buffer; + if (Error E = writeFile(HostOutPath, + StringRef(BinaryData.begin(), BinaryData.size()))) + return false; + return true; +} + +extern "C" bool LLVMRustOffloadEmbedBufferInModule(LLVMModuleRef HostM, + const char *HostOutPath) { + auto MBOrErr = MemoryBuffer::getFile(HostOutPath); + if (!MBOrErr) { + auto E = MBOrErr.getError(); + auto _B = errorCodeToError(E); + return false; + } + MemoryBufferRef Buf = (*MBOrErr)->getMemBufferRef(); + Module *M = unwrap(HostM); + StringRef SectionName = ".llvm.offloading"; + Align Alignment = Align(8); + llvm::embedBufferInModule(*M, Buf, SectionName, Alignment); + return true; +} + +// Clone OldFn into NewFn, remapping its arguments to RebuiltArgs. +// Each arg of OldFn is replaced with the corresponding value in RebuiltArgs. +// For scalars, RebuiltArgs contains the value cast and/or truncated to the +// original type. +extern "C" void LLVMRustOffloadMapper(LLVMValueRef OldFn, LLVMValueRef NewFn, + const LLVMValueRef *RebuiltArgs) { + llvm::Function *oldFn = llvm::unwrap(OldFn); + llvm::Function *newFn = llvm::unwrap(NewFn); + + // Map old arguments to new arguments. We skip the first dyn_ptr argument, + // since it can't be used directly by user code. + llvm::ValueToValueMapTy vmap; + auto newArgIt = newFn->arg_begin(); + newArgIt->setName("dyn_ptr"); + + unsigned i = 0; + for (auto &oldArg : oldFn->args()) { + vmap[&oldArg] = unwrap(RebuiltArgs[i++]); + } + + llvm::SmallVector returns; + llvm::CloneFunctionInto(newFn, oldFn, vmap, + llvm::CloneFunctionChangeType::LocalChangesOnly, + returns); + + BasicBlock &entry = newFn->getEntryBlock(); + BasicBlock &clonedEntry = *std::next(newFn->begin()); + + if (entry.getTerminator()) + entry.getTerminator()->eraseFromParent(); + + IRBuilder<> B(&entry); + B.CreateBr(&clonedEntry); +} diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 021a652a5ac50..25c3df3ae541e 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -2275,10 +2275,18 @@ impl CommandLineStep for Assemble { if builder.config.llvm_offload && !builder.config.dry_run() { debug!("`llvm_offload` requested"); + let rust_offload = builder.ensure(llvm::RustOffload { target: build_compiler.host }); let offload_install = builder.ensure(llvm::OmpOffload { target: build_compiler.host }); if let Some(_llvm_config) = builder.llvm_config(builder.config.host_target) { let target_libdir = builder.sysroot_target_libdir(target_compiler, target_compiler.host); + let rust_offload_dst_lib = target_libdir.join(rust_offload.rust_offload_filename()); + builder.copy_link( + &rust_offload.rust_offload_path(), + &rust_offload_dst_lib, + FileType::NativeLibrary, + ); + for p in offload_install.offload_paths() { let libname = p.file_name().unwrap(); let dst_lib = target_libdir.join(libname); diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index d3276cfb5371b..2e14082310350 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -942,6 +942,96 @@ fn get_var(var_base: &str, host: &str, target: &str) -> Option { .or_else(|| env::var_os(var_base)) } +#[derive(Clone)] +pub struct BuiltRustOffload { + /// Path to the rust offload dylib + offload: PathBuf, +} + +impl BuiltRustOffload { + pub fn rust_offload_path(&self) -> PathBuf { + self.offload.clone() + } + + pub fn rust_offload_filename(&self) -> String { + self.offload.file_name().unwrap().to_str().unwrap().to_owned() + } +} + +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct RustOffload { + pub target: TargetSelection, +} + +impl CommandLineStep for RustOffload { + type Output = BuiltRustOffload; + const IS_HOST: bool = true; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.alias("rust-offload") + } + + fn make_run(run: RunConfig<'_>) { + run.builder.ensure(RustOffload { target: run.target }); + } + + fn run(self, builder: &Builder<'_>) -> Self::Output { + if builder.config.dry_run() { + return BuiltRustOffload { + offload: builder.config.tempdir().join("rust-offload-dry-run"), + }; + } + + let target = self.target; + + let LlvmResult { host_llvm_config, llvm_cmake_dir } = builder.ensure(Llvm { target }); + + let out_dir = builder.rust_offload_out(target); + + let llvm_version_major = llvm::get_llvm_version_major(builder, &host_llvm_config); + let lib_ext = std::env::consts::DLL_EXTENSION; + let lib_rust_offload = format!("libRustOffload-{llvm_version_major}"); + let build_dir = out_dir.join(libdir(target)); + let dylib = build_dir.join(&lib_rust_offload).with_extension(lib_ext); + + let mut cfg = + cmake::Config::new(builder.src.join("compiler/rustc_llvm/llvm-wrapper/offload/")); + + // Logic copied from `configure_llvm` + // ThinLTO is only available when building with LLVM, enabling LLD is required. + // Apple's linker ld64 supports ThinLTO out of the box though, so don't use LLD on Darwin. + let mut ldflags = LdFlags::default(); + if builder.config.llvm_thin_lto && !target.contains("apple") { + ldflags.push_all("-fuse-ld=lld"); + } + + configure_cmake(builder, target, &mut cfg, true, ldflags, CcFlags::default(), &[]); + + let profile = match (builder.config.llvm_optimize, builder.config.llvm_release_debuginfo) { + (false, _) => "Debug", + (true, false) => "Release", + (true, true) => "RelWithDebInfo", + }; + + cfg.out_dir(&out_dir) + .profile(profile) + .env("LLVM_CONFIG_REAL", &host_llvm_config) + .define("LLVM_DIR", llvm_cmake_dir); + + cfg.build(); + + if !dylib.exists() { + eprintln!( + "`{lib_rust_offload}` not found in `{}`. Either the build has failed or RustOffload was built with a wrong version of LLVM", + build_dir.display() + ); + exit!(1); + } + + BuiltRustOffload { offload: dylib } + } +} + #[derive(Clone)] pub struct BuiltOmpOffload { /// Path to the omp and offload dylibs. @@ -998,7 +1088,7 @@ impl CommandLineStep for OmpOffload { // Running cmake twice in the same folder is known to cause issues, like deleting existing // binaries. We therefore write our offload artifacts into it's own folder, instead of // using the llvm build dir. - let out_dir = builder.offload_out(target); + let out_dir = builder.omp_offload_out(target); let mut files = vec![]; let lib_ext = std::env::consts::DLL_EXTENSION; diff --git a/src/bootstrap/src/core/builder/mod.rs b/src/bootstrap/src/core/builder/mod.rs index 051e01a0a6666..22adbfd946965 100644 --- a/src/bootstrap/src/core/builder/mod.rs +++ b/src/bootstrap/src/core/builder/mod.rs @@ -823,6 +823,7 @@ impl<'a> Builder<'a> { tool::CargoMiri, llvm::Lld, llvm::Enzyme, + llvm::RustOffload, llvm::CrtBeginEnd, tool::RustdocGUITest, tool::OptimizedDist, diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index 7d119247b3bac..27a03b1616192 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -983,10 +983,14 @@ impl Build { self.out.join(&*target.triple).join("enzyme") } - fn offload_out(&self, target: TargetSelection) -> PathBuf { + fn omp_offload_out(&self, target: TargetSelection) -> PathBuf { self.out.join(&*target.triple).join("offload") } + fn rust_offload_out(&self, target: TargetSelection) -> PathBuf { + self.out.join(&*target.triple).join("rust-offload") + } + fn lld_out(&self, target: TargetSelection) -> PathBuf { self.out.join(target).join("lld") } From 93a46b6e969575da44d3900d9d6e906f2254305f Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 7 Aug 2026 09:08:24 +1000 Subject: [PATCH 091/100] Improve the canonical param env cache Currently it is modified with the very clunky `canonical_param_env_cache_get_or_insert` method, which takes two closures. This commit replaces that with `with_canonical_param_env_cache` a simpler accessor that is very similar to the nearby `with_global_cache`. This lets `canonicalize_param_env` use normal hash map operations. The commit also: - Introduces a dedicated `CanonicalParamEnvCache` newtype. - Adds a helpful comment to `CanonicalizeParamEnvCacheEntry::param_env`. --- compiler/rustc_middle/src/ty/context.rs | 3 +- .../src/ty/context/impl_interner.rs | 10 ++---- .../src/canonical/canonicalizer.rs | 34 +++++++++---------- compiler/rustc_type_ir/src/canonical.rs | 7 ++++ compiler/rustc_type_ir/src/interner.rs | 10 +++--- 5 files changed, 31 insertions(+), 33 deletions(-) diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index f834784f98847..30ee1d945dc18 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -749,8 +749,7 @@ pub struct GlobalCtxt<'tcx> { /// Caches the results of goal evaluation in the new solver. pub new_solver_evaluation_cache: Lock>>, - pub new_solver_canonical_param_env_cache: - Lock, ty::CanonicalParamEnvCacheEntry>>>, + pub new_solver_canonical_param_env_cache: Lock>>, pub canonical_param_env_cache: CanonicalParamEnvCache<'tcx>, diff --git a/compiler/rustc_middle/src/ty/context/impl_interner.rs b/compiler/rustc_middle/src/ty/context/impl_interner.rs index 470abf327679f..ecc8d8867a279 100644 --- a/compiler/rustc_middle/src/ty/context/impl_interner.rs +++ b/compiler/rustc_middle/src/ty/context/impl_interner.rs @@ -146,15 +146,11 @@ impl<'tcx> Interner for TyCtxt<'tcx> { f(&mut *self.new_solver_evaluation_cache.lock()) } - fn canonical_param_env_cache_get_or_insert( + fn with_canonical_param_env_cache( self, - param_env: ty::ParamEnv<'tcx>, - f: impl FnOnce() -> ty::CanonicalParamEnvCacheEntry, - from_entry: impl FnOnce(&ty::CanonicalParamEnvCacheEntry) -> R, + f: impl FnOnce(&mut ty::CanonicalParamEnvCache) -> R, ) -> R { - let mut cache = self.new_solver_canonical_param_env_cache.lock(); - let entry = cache.entry(param_env).or_insert_with(f); - from_entry(entry) + f(&mut *self.new_solver_canonical_param_env_cache.lock()) } fn assert_evaluation_is_concurrent(&self) { diff --git a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs index 20402649ceabd..385741a6f1b3d 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs @@ -132,9 +132,8 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { // globally cached. We don't rely on any additional information when canonicalizing // placeholders. if !param_env.has_non_region_infer() { - delegate.cx().canonical_param_env_cache_get_or_insert( - param_env, - || { + delegate.cx().with_canonical_param_env_cache(|cache| { + let entry = cache.0.entry(param_env).or_insert_with(|| { let mut env_canonicalizer = Canonicalizer { delegate, canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv), @@ -154,21 +153,20 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { var_kinds: env_canonicalizer.var_kinds, variables: env_canonicalizer.variables, } - }, - |&CanonicalParamEnvCacheEntry { - param_env, - variables: ref cache_variables, - ref variable_lookup_table, - ref var_kinds, - }| { - // FIXME(nnethercote): for reasons I don't understand, this `new`+`extend` - // combination is faster than `variables.clone()`, because it somehow avoids - // some allocations. - let mut variables = ThinVec::new(); - variables.extend(cache_variables.iter().copied()); - (param_env, variables, var_kinds.clone(), variable_lookup_table.clone()) - }, - ) + }); + + // FIXME(nnethercote): for reasons I don't understand, this `new`+`extend` + // combination is faster than `variables.clone()`, because it somehow avoids + // some allocations. + let mut variables = ThinVec::new(); + variables.extend(entry.variables.iter().copied()); + ( + entry.param_env, + variables, + entry.var_kinds.clone(), + entry.variable_lookup_table.clone(), + ) + }) } else { let mut env_canonicalizer = Canonicalizer { delegate, diff --git a/compiler/rustc_type_ir/src/canonical.rs b/compiler/rustc_type_ir/src/canonical.rs index e0cbc0890b761..e76d6b86d5e5c 100644 --- a/compiler/rustc_type_ir/src/canonical.rs +++ b/compiler/rustc_type_ir/src/canonical.rs @@ -364,8 +364,15 @@ impl Index for CanonicalVarValues { } } +#[derive_where(Default; I: Interner)] +pub struct CanonicalParamEnvCache( + pub HashMap>, +); + #[derive_where(Clone, Debug; I: Interner)] pub struct CanonicalParamEnvCacheEntry { + // Note: this `param_env` is the canonicalized form of the key for this entry in the enclosing + // `CanonicalParamEnvCache`. pub param_env: I::ParamEnv, pub variables: ThinVec, pub variable_lookup_table: HashMap, diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index 9ca1ec19a1339..56a911bdb4b8b 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -18,8 +18,8 @@ use crate::solve::{ }; use crate::visit::{Flags, TypeVisitable}; use crate::{ - self as ty, BoundRegion, BoundVar, CanonicalParamEnvCacheEntry, DebruijnIndex, Region, - RegionKind, TraitRef, search_graph, + self as ty, BoundRegion, BoundVar, CanonicalParamEnvCache, DebruijnIndex, Region, RegionKind, + TraitRef, search_graph, }; #[cfg_attr(feature = "nightly", rustc_diagnostic_item = "type_ir_interner")] @@ -204,11 +204,9 @@ pub trait Interner: fn with_global_cache(self, f: impl FnOnce(&mut search_graph::GlobalCache) -> R) -> R; - fn canonical_param_env_cache_get_or_insert( + fn with_canonical_param_env_cache( self, - param_env: Self::ParamEnv, - f: impl FnOnce() -> CanonicalParamEnvCacheEntry, - from_entry: impl FnOnce(&CanonicalParamEnvCacheEntry) -> R, + f: impl FnOnce(&mut CanonicalParamEnvCache) -> R, ) -> R; /// Useful for testing. If a cache entry is replaced, this should From 8ba2b46c78bfc18068ee07393ba2a5e4955aabbc Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 7 Aug 2026 09:15:04 +1000 Subject: [PATCH 092/100] Update a comment I now understand what is happening here. --- .../rustc_next_trait_solver/src/canonical/canonicalizer.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs index 385741a6f1b3d..78d8a0dbba708 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs @@ -155,9 +155,9 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { } }); - // FIXME(nnethercote): for reasons I don't understand, this `new`+`extend` - // combination is faster than `variables.clone()`, because it somehow avoids - // some allocations. + // The obvious thing to do here is `variables.clone()`. But this `new`+`extend` + // combination results in the variables having more spare capacity, which avoids + // some later allocations and makes things a little faster. let mut variables = ThinVec::new(); variables.extend(entry.variables.iter().copied()); ( From 87d95f53a79e89cb669800db46f982956b40b26f Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 7 Aug 2026 10:01:50 +1000 Subject: [PATCH 093/100] Introduce `Canonicalizer::new` It avoids some repetition. --- .../src/canonical/canonicalizer.rs | 70 ++++++++----------- 1 file changed, 29 insertions(+), 41 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs index 78d8a0dbba708..1ebbeaec482e2 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs @@ -83,23 +83,25 @@ pub(super) struct Canonicalizer<'a, D: SolverDelegate, I: Interner } impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { - pub(super) fn canonicalize_response>( - delegate: &'a D, - max_input_universe: ty::UniverseIndex, - value: T, - ) -> ty::Canonical { - let mut canonicalizer = Canonicalizer { + fn new(delegate: &'a D, canonicalize_mode: CanonicalizeMode) -> Self { + Canonicalizer { delegate, - canonicalize_mode: CanonicalizeMode::Response { max_input_universe }, - + canonicalize_mode, variables: Default::default(), variable_lookup_table: Default::default(), sub_root_lookup_table: Default::default(), var_kinds: Default::default(), - cache: Default::default(), - }; + } + } + pub(super) fn canonicalize_response>( + delegate: &'a D, + max_input_universe: ty::UniverseIndex, + value: T, + ) -> ty::Canonical { + let mut canonicalizer = + Canonicalizer::new(delegate, CanonicalizeMode::Response { max_input_universe }); let value = if value.has_type_flags(NEEDS_CANONICAL) { value.fold_with(&mut canonicalizer) } else { @@ -134,17 +136,10 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { if !param_env.has_non_region_infer() { delegate.cx().with_canonical_param_env_cache(|cache| { let entry = cache.0.entry(param_env).or_insert_with(|| { - let mut env_canonicalizer = Canonicalizer { + let mut env_canonicalizer = Canonicalizer::new( delegate, - canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv), - - variables: Default::default(), - variable_lookup_table: Default::default(), - sub_root_lookup_table: Default::default(), - var_kinds: Default::default(), - - cache: Default::default(), - }; + CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv), + ); let param_env = param_env.fold_with(&mut env_canonicalizer); debug_assert!(env_canonicalizer.sub_root_lookup_table.is_empty()); CanonicalParamEnvCacheEntry { @@ -168,17 +163,10 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { ) }) } else { - let mut env_canonicalizer = Canonicalizer { + let mut env_canonicalizer = Canonicalizer::new( delegate, - canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv), - - variables: Default::default(), - variable_lookup_table: Default::default(), - sub_root_lookup_table: Default::default(), - var_kinds: Default::default(), - - cache: Default::default(), - }; + CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv), + ); let param_env = param_env.fold_with(&mut env_canonicalizer); debug_assert!(env_canonicalizer.sub_root_lookup_table.is_empty()); ( @@ -205,23 +193,23 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { // First canonicalize the `param_env` while keeping `'static` let (param_env, variables, var_kinds, variable_lookup_table) = Canonicalizer::canonicalize_param_env(delegate, input.goal.param_env); + // Then canonicalize the rest of the input without keeping `'static` // while *mostly* reusing the canonicalizer from above. + // + // We do not reuse the cache as it may contain entries whose canonicalized + // value contains `'static`. While we could alternatively handle this by + // checking for `'static` when using cached entries, this does not + // feel worth the effort. I do not expect that a `ParamEnv` will ever + // contain large enough types for caching to be necessary. let mut rest_canonicalizer = Canonicalizer { - delegate, - canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::Predicate), - variables, variable_lookup_table, - sub_root_lookup_table: Default::default(), var_kinds, - - // We do not reuse the cache as it may contain entries whose canonicalized - // value contains `'static`. While we could alternatively handle this by - // checking for `'static` when using cached entries, this does not - // feel worth the effort. I do not expect that a `ParamEnv` will ever - // contain large enough types for caching to be necessary. - cache: Default::default(), + ..Canonicalizer::new( + delegate, + CanonicalizeMode::Input(CanonicalizeInputKind::Predicate), + ) }; let predicate = input.goal.predicate; From 8b5cad12dd5b0b8ac225811a78d5319fe6eeb198 Mon Sep 17 00:00:00 2001 From: Senthilnathan Date: Fri, 7 Aug 2026 10:42:36 +0530 Subject: [PATCH 094/100] Add note to invalidate iterator when mutating inside a for-loop --- .../src/diagnostics/conflict_errors.rs | 92 +++++++++++++++++++ .../vec-mut-iter-borrow.stderr | 19 ++-- .../borrowck-for-loop-head-linkage.stderr | 44 +++++---- tests/ui/borrowck/issue-82462.stderr | 25 +++-- .../ui/borrowck/mutate-vec-while-iterating.rs | 21 +++++ .../mutate-vec-while-iterating.stderr | 40 ++++++++ ...ng-updating-cursor-issue-108704.nll.stderr | 19 +++- ...dating-cursor-issue-108704.polonius.stderr | 19 +++- tests/ui/suggestions/issue-102972.stderr | 40 ++++---- 9 files changed, 260 insertions(+), 59 deletions(-) create mode 100644 tests/ui/borrowck/mutate-vec-while-iterating.rs create mode 100644 tests/ui/borrowck/mutate-vec-while-iterating.stderr diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs index e3e36f9bbc715..d716c2d57039e 100644 --- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs +++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs @@ -1880,6 +1880,14 @@ impl<'diag, 'tcx> MirBorrowckCtxt<'_, 'diag, 'tcx> { issued_borrow.borrowed_place, &issued_spans, ); + self.explain_iterator_invalidation_in_for_loop_if_applicable( + &mut err, + &issued_spans, + place, + issued_borrow.borrowed_place, + issued_borrow.kind, + span, + ); err } @@ -1903,6 +1911,14 @@ impl<'diag, 'tcx> MirBorrowckCtxt<'_, 'diag, 'tcx> { span, issued_span, ); + self.explain_iterator_invalidation_in_for_loop_if_applicable( + &mut err, + &issued_spans, + place, + issued_borrow.borrowed_place, + issued_borrow.kind, + span, + ); self.suggest_using_closure_argument_instead_of_capture( &mut err, issued_borrow.borrowed_place, @@ -2618,6 +2634,50 @@ impl<'diag, 'tcx> MirBorrowckCtxt<'_, 'diag, 'tcx> { } } + /// Explain iterator invalidation when mutating a collection in a for loop. + /// + /// For example: + /// ```compile_fail + /// let mut values = vec![1, 2, 3]; + /// for value in &values { + /// values.push(4); + /// } + /// ``` + fn explain_iterator_invalidation_in_for_loop_if_applicable( + &self, + err: &mut Diag<'_>, + issued_spans: &UseSpans<'tcx>, + place: Place<'tcx>, + borrowed_place: Place<'tcx>, + borrow_kind: BorrowKind, + gen_span: Span, + ) { + let issue_span = issued_spans.args_or_use(); + let tcx = self.infcx.tcx; + + let Some(body_id) = tcx.hir_node(self.mir_hir_id()).body_id() else { return }; + + if let Some(for_span) = find_for_loop_span(tcx, body_id, issue_span) + && place.local == borrowed_place.local + && for_span.contains(gen_span) + { + let place_desc = self.describe_any_place(place.as_ref()); + let borrow_kind_str = + if matches!(borrow_kind, BorrowKind::Mut { .. }) { "mutably" } else { "immutably" }; + err.span_label( + for_span, + format!( + "this for loop borrows {place_desc} {borrow_kind_str}, \ + preventing mutation within its body" + ), + ); + err.help( + "consider using an index-based loop instead, or collecting \ + modifications into a separate collection", + ); + } + } + /// Suggest using closure argument instead of capture. /// /// For example: @@ -4654,6 +4714,38 @@ enum AnnotatedBorrowFnSignature<'tcx> { }, } +/// Find the `Match` expression desugared from a for loop, whose +/// `IntoIter::into_iter` argument contains `issue_span`. +/// Returns the for-loop match expression span. +fn find_for_loop_span<'hir>( + tcx: TyCtxt<'hir>, + body_id: hir::BodyId, + issue_span: Span, +) -> Option { + struct ExprFinder<'hir> { + tcx: TyCtxt<'hir>, + issue_span: Span, + result: Option, + } + impl<'hir> Visitor<'hir> for ExprFinder<'hir> { + fn visit_expr(&mut self, ex: &'hir hir::Expr<'hir>) { + if let hir::ExprKind::Match(scrutinee, _, hir::MatchSource::ForLoopDesugar) = ex.kind + && let hir::ExprKind::Call(path, [arg]) = scrutinee.kind + && let hir::ExprKind::Path(qpath) = path.kind + && self.tcx.qpath_is_lang_item(qpath, LangItem::IntoIterIntoIter) + && arg.span.contains(self.issue_span) + { + self.result = Some(ex.span); + return; + } + hir::intravisit::walk_expr(self, ex); + } + } + let mut finder = ExprFinder { tcx, issue_span, result: None }; + finder.visit_expr(tcx.hir_body(body_id).value); + finder.result +} + impl<'tcx> AnnotatedBorrowFnSignature<'tcx> { /// Annotate the provided diagnostic with information about borrow from the fn signature that /// helps explain. diff --git a/tests/ui/array-slice-vec/vec-mut-iter-borrow.stderr b/tests/ui/array-slice-vec/vec-mut-iter-borrow.stderr index d9343140fb1dc..7d32670effe3f 100644 --- a/tests/ui/array-slice-vec/vec-mut-iter-borrow.stderr +++ b/tests/ui/array-slice-vec/vec-mut-iter-borrow.stderr @@ -1,13 +1,18 @@ error[E0499]: cannot borrow `xs` as mutable more than once at a time --> $DIR/vec-mut-iter-borrow.rs:5:9 | -LL | for x in &mut xs { - | ------- - | | - | first mutable borrow occurs here - | first borrow later used here -LL | xs.push(1) - | ^^ second mutable borrow occurs here +LL | for x in &mut xs { + | - ------- + | | | + | | first mutable borrow occurs here + | _____| first borrow later used here + | | +LL | | xs.push(1) + | | ^^ second mutable borrow occurs here +LL | | } + | |_____- this for loop borrows `xs` mutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection error: aborting due to 1 previous error diff --git a/tests/ui/borrowck/borrowck-for-loop-head-linkage.stderr b/tests/ui/borrowck/borrowck-for-loop-head-linkage.stderr index f47dce453696e..8cdb2a0f8878d 100644 --- a/tests/ui/borrowck/borrowck-for-loop-head-linkage.stderr +++ b/tests/ui/borrowck/borrowck-for-loop-head-linkage.stderr @@ -1,26 +1,38 @@ error[E0502]: cannot borrow `vector` as mutable because it is also borrowed as immutable --> $DIR/borrowck-for-loop-head-linkage.rs:7:9 | -LL | for &x in &vector { - | ------- - | | - | immutable borrow occurs here - | immutable borrow later used here -LL | let cap = vector.capacity(); -LL | vector.extend(repeat(0)); - | ^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here +LL | for &x in &vector { + | - ------- + | | | + | | immutable borrow occurs here + | _____| immutable borrow later used here + | | +LL | | let cap = vector.capacity(); +LL | | vector.extend(repeat(0)); + | | ^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here +LL | | vector[1] = 5; +LL | | } + | |_____- this for loop borrows `vector` immutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection error[E0502]: cannot borrow `vector` as mutable because it is also borrowed as immutable --> $DIR/borrowck-for-loop-head-linkage.rs:8:9 | -LL | for &x in &vector { - | ------- - | | - | immutable borrow occurs here - | immutable borrow later used here -... -LL | vector[1] = 5; - | ^^^^^^ mutable borrow occurs here +LL | for &x in &vector { + | - ------- + | | | + | | immutable borrow occurs here + | _____| immutable borrow later used here + | | +LL | | let cap = vector.capacity(); +LL | | vector.extend(repeat(0)); +LL | | vector[1] = 5; + | | ^^^^^^ mutable borrow occurs here +LL | | } + | |_____- this for loop borrows `vector` immutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection error: aborting due to 2 previous errors diff --git a/tests/ui/borrowck/issue-82462.stderr b/tests/ui/borrowck/issue-82462.stderr index 8cb4583eba940..ed5a4cc2d219f 100644 --- a/tests/ui/borrowck/issue-82462.stderr +++ b/tests/ui/borrowck/issue-82462.stderr @@ -1,17 +1,22 @@ error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable --> $DIR/issue-82462.rs:18:9 | -LL | for x in DroppingSlice(&*v).iter() { - | ------------------ - | | | - | | immutable borrow occurs here - | a temporary with access to the immutable borrow is created here ... -LL | v.push(*x); - | ^^^^^^^^^^ mutable borrow occurs here -LL | break; -LL | } - | - ... and the immutable borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `DroppingSlice` +LL | for x in DroppingSlice(&*v).iter() { + | - ------------------ + | | | | + | | | immutable borrow occurs here + | _____| a temporary with access to the immutable borrow is created here ... + | | +LL | | v.push(*x); + | | ^^^^^^^^^^ mutable borrow occurs here +LL | | break; +LL | | } + | | - + | | | + | |_____... and the immutable borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `DroppingSlice` + | this for loop borrows `v` immutably, preventing mutation within its body | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection help: consider adding semicolon after the expression so its temporaries are dropped sooner, before the local variables declared by the block are dropped | LL | }; diff --git a/tests/ui/borrowck/mutate-vec-while-iterating.rs b/tests/ui/borrowck/mutate-vec-while-iterating.rs new file mode 100644 index 0000000000000..18cc906ee500b --- /dev/null +++ b/tests/ui/borrowck/mutate-vec-while-iterating.rs @@ -0,0 +1,21 @@ +// Regression test for https://github.com/rust-lang/rust/issues/159489 + +fn main() { + let mut values = vec![1, 2, 3]; + + for value in &values { + if *value == 2 { + values.push(4); //~ ERROR E0502 + } + } +} + +fn mutate_while_iterating_mut() { + let mut values = vec![1, 2, 3]; + + for value in &mut values { + if *value == 2 { + values.push(4); //~ ERROR E0499 + } + } +} diff --git a/tests/ui/borrowck/mutate-vec-while-iterating.stderr b/tests/ui/borrowck/mutate-vec-while-iterating.stderr new file mode 100644 index 0000000000000..01f42e9689ab7 --- /dev/null +++ b/tests/ui/borrowck/mutate-vec-while-iterating.stderr @@ -0,0 +1,40 @@ +error[E0502]: cannot borrow `values` as mutable because it is also borrowed as immutable + --> $DIR/mutate-vec-while-iterating.rs:8:13 + | +LL | for value in &values { + | - ------- + | | | + | | immutable borrow occurs here + | _____| immutable borrow later used here + | | +LL | | if *value == 2 { +LL | | values.push(4); + | | ^^^^^^^^^^^^^^ mutable borrow occurs here +LL | | } +LL | | } + | |_____- this for loop borrows `values` immutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection + +error[E0499]: cannot borrow `values` as mutable more than once at a time + --> $DIR/mutate-vec-while-iterating.rs:18:13 + | +LL | for value in &mut values { + | - ----------- + | | | + | | first mutable borrow occurs here + | _____| first borrow later used here + | | +LL | | if *value == 2 { +LL | | values.push(4); + | | ^^^^^^ second mutable borrow occurs here +LL | | } +LL | | } + | |_____- this for loop borrows `values` mutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0499, E0502. +For more information about an error, try `rustc --explain E0499`. diff --git a/tests/ui/nll/polonius/iterating-updating-cursor-issue-108704.nll.stderr b/tests/ui/nll/polonius/iterating-updating-cursor-issue-108704.nll.stderr index b768f60590cb0..59e5c0502cc0e 100644 --- a/tests/ui/nll/polonius/iterating-updating-cursor-issue-108704.nll.stderr +++ b/tests/ui/nll/polonius/iterating-updating-cursor-issue-108704.nll.stderr @@ -1,11 +1,20 @@ error[E0499]: cannot borrow `*elements` as mutable more than once at a time --> $DIR/iterating-updating-cursor-issue-108704.rs:41:26 | -LL | for (idx, el) in elements.iter_mut().enumerate() { - | ^^^^^^^^ - | | - | `*elements` was mutably borrowed here in the previous iteration of the loop - | first borrow used here, in later iteration of loop +LL | for (idx, el) in elements.iter_mut().enumerate() { + | - ^^^^^^^^ + | | | + | | `*elements` was mutably borrowed here in the previous iteration of the loop + | _________| first borrow used here, in later iteration of loop + | | +LL | | if el.name == *p { +LL | | elements = &mut el.children; +LL | | break; +LL | | } +LL | | } + | |_________- this for loop borrows `*elements` mutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection error: aborting due to 1 previous error diff --git a/tests/ui/nll/polonius/iterating-updating-cursor-issue-108704.polonius.stderr b/tests/ui/nll/polonius/iterating-updating-cursor-issue-108704.polonius.stderr index b768f60590cb0..59e5c0502cc0e 100644 --- a/tests/ui/nll/polonius/iterating-updating-cursor-issue-108704.polonius.stderr +++ b/tests/ui/nll/polonius/iterating-updating-cursor-issue-108704.polonius.stderr @@ -1,11 +1,20 @@ error[E0499]: cannot borrow `*elements` as mutable more than once at a time --> $DIR/iterating-updating-cursor-issue-108704.rs:41:26 | -LL | for (idx, el) in elements.iter_mut().enumerate() { - | ^^^^^^^^ - | | - | `*elements` was mutably borrowed here in the previous iteration of the loop - | first borrow used here, in later iteration of loop +LL | for (idx, el) in elements.iter_mut().enumerate() { + | - ^^^^^^^^ + | | | + | | `*elements` was mutably borrowed here in the previous iteration of the loop + | _________| first borrow used here, in later iteration of loop + | | +LL | | if el.name == *p { +LL | | elements = &mut el.children; +LL | | break; +LL | | } +LL | | } + | |_________- this for loop borrows `*elements` mutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection error: aborting due to 1 previous error diff --git a/tests/ui/suggestions/issue-102972.stderr b/tests/ui/suggestions/issue-102972.stderr index 438f28ad03264..1ff972fea0610 100644 --- a/tests/ui/suggestions/issue-102972.stderr +++ b/tests/ui/suggestions/issue-102972.stderr @@ -1,14 +1,18 @@ error[E0499]: cannot borrow `chars` as mutable more than once at a time --> $DIR/issue-102972.rs:6:9 | -LL | for _c in chars.by_ref() { - | -------------- - | | - | first mutable borrow occurs here - | first borrow later used here -LL | chars.next(); - | ^^^^^ second mutable borrow occurs here - | +LL | for _c in chars.by_ref() { + | - -------------- + | | | + | | first mutable borrow occurs here + | _____| first borrow later used here + | | +LL | | chars.next(); + | | ^^^^^ second mutable borrow occurs here +LL | | } + | |_____- this for loop borrows `chars` mutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection = note: a for loop advances the iterator for you, the result is stored in `_c` help: if you want to call `next` on a iterator within the loop, consider using `while let` | @@ -39,14 +43,18 @@ LL + while let Some(_i) = iter.next() { error[E0499]: cannot borrow `i` as mutable more than once at a time --> $DIR/issue-102972.rs:22:9 | -LL | for () in i.by_ref() { - | ---------- - | | - | first mutable borrow occurs here - | first borrow later used here -LL | i.next(); - | ^ second mutable borrow occurs here - | +LL | for () in i.by_ref() { + | - ---------- + | | | + | | first mutable borrow occurs here + | _____| first borrow later used here + | | +LL | | i.next(); + | | ^ second mutable borrow occurs here +LL | | } + | |_____- this for loop borrows `i` mutably, preventing mutation within its body + | + = help: consider using an index-based loop instead, or collecting modifications into a separate collection = note: a for loop advances the iterator for you, the result is stored in its pattern help: if you want to call `next` on a iterator within the loop, consider using `while let` | From 5c0a7328be12489048b7cff4092b94dbec93b768 Mon Sep 17 00:00:00 2001 From: zakrad <49591476+zakrad@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:48:17 +0330 Subject: [PATCH 095/100] Add regression test for unknown feature name with other errors present --- .../unknown-feature-with-other-errors-58390.rs | 16 ++++++++++++++++ ...nown-feature-with-other-errors-58390.stderr | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 tests/ui/feature-gates/unknown-feature-with-other-errors-58390.rs create mode 100644 tests/ui/feature-gates/unknown-feature-with-other-errors-58390.stderr diff --git a/tests/ui/feature-gates/unknown-feature-with-other-errors-58390.rs b/tests/ui/feature-gates/unknown-feature-with-other-errors-58390.rs new file mode 100644 index 0000000000000..f14c0937360f4 --- /dev/null +++ b/tests/ui/feature-gates/unknown-feature-with-other-errors-58390.rs @@ -0,0 +1,16 @@ +//! Regression test for . +//! +//! An unknown `#![feature(..)]` name used to be silently ignored whenever the crate had any +//! other error, because the check only ran during stability checking. Both errors must be +//! reported. + +#![feature(this_feature_does_not_exist)] //~ ERROR unknown feature `this_feature_does_not_exist` + +struct Foo; + +trait Bar {} + +impl Bar for Foo {} +impl Bar for Foo {} //~ ERROR conflicting implementations of trait `Bar` for type `Foo` + +fn main() {} diff --git a/tests/ui/feature-gates/unknown-feature-with-other-errors-58390.stderr b/tests/ui/feature-gates/unknown-feature-with-other-errors-58390.stderr new file mode 100644 index 0000000000000..4560bd48f58ce --- /dev/null +++ b/tests/ui/feature-gates/unknown-feature-with-other-errors-58390.stderr @@ -0,0 +1,18 @@ +error[E0635]: unknown feature `this_feature_does_not_exist` + --> $DIR/unknown-feature-with-other-errors-58390.rs:7:12 + | +LL | #![feature(this_feature_does_not_exist)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0119]: conflicting implementations of trait `Bar` for type `Foo` + --> $DIR/unknown-feature-with-other-errors-58390.rs:14:1 + | +LL | impl Bar for Foo {} + | ---------------- first implementation here +LL | impl Bar for Foo {} + | ^^^^^^^^^^^^^^^^ conflicting implementation for `Foo` + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0119, E0635. +For more information about an error, try `rustc --explain E0119`. From 2a773d215e9785c877e81cfbeee581056f1e0f12 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 6 Aug 2026 20:10:35 +0200 Subject: [PATCH 096/100] make test use minicore --- .../asm/naked-functions/unused.aarch64.stderr | 22 +++++++++---------- tests/ui/asm/naked-functions/unused.rs | 19 +++++++++++----- .../asm/naked-functions/unused.x86_64.stderr | 22 +++++++++---------- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/tests/ui/asm/naked-functions/unused.aarch64.stderr b/tests/ui/asm/naked-functions/unused.aarch64.stderr index bfb2923b0b8d6..366d338d15b48 100644 --- a/tests/ui/asm/naked-functions/unused.aarch64.stderr +++ b/tests/ui/asm/naked-functions/unused.aarch64.stderr @@ -1,66 +1,66 @@ error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:16:32 + --> $DIR/unused.rs:23:32 | LL | pub extern "C" fn function(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: the lint level is defined here - --> $DIR/naked-functions-unused.rs:5:9 + --> $DIR/unused.rs:11:9 | LL | #![deny(unused)] | ^^^^^^ = note: `#[deny(unused_variables)]` implied by `#[deny(unused)]` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:16:42 + --> $DIR/unused.rs:23:42 | LL | pub extern "C" fn function(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:27:38 + --> $DIR/unused.rs:34:38 | LL | pub extern "C" fn associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:27:48 + --> $DIR/unused.rs:34:48 | LL | pub extern "C" fn associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:35:41 + --> $DIR/unused.rs:42:41 | LL | pub extern "C" fn method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:35:51 + --> $DIR/unused.rs:42:51 | LL | pub extern "C" fn method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:45:40 + --> $DIR/unused.rs:52:40 | LL | extern "C" fn trait_associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:45:50 + --> $DIR/unused.rs:52:50 | LL | extern "C" fn trait_associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/naked-functions-unused.rs:53:43 + --> $DIR/unused.rs:60:43 | LL | extern "C" fn trait_method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/naked-functions-unused.rs:53:53 + --> $DIR/unused.rs:60:53 | LL | extern "C" fn trait_method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` diff --git a/tests/ui/asm/naked-functions/unused.rs b/tests/ui/asm/naked-functions/unused.rs index 945ab1a40ad0c..51e3e90c1a72e 100644 --- a/tests/ui/asm/naked-functions/unused.rs +++ b/tests/ui/asm/naked-functions/unused.rs @@ -1,9 +1,16 @@ +//@ add-minicore //@ revisions: x86_64 aarch64 -//@ needs-asm-support -//@[x86_64] only-x86_64 -//@[aarch64] only-aarch64 -#![deny(unused)] +//@[x86_64] compile-flags: --target x86_64-unknown-linux-gnu +//@[x86_64] needs-llvm-components: x86 +//@[aarch64] compile-flags: --target aarch64-unknown-linux-gnu +//@[aarch64] needs-llvm-components: aarch64 +//@ ignore-backends: gcc #![crate_type = "lib"] +#![feature(no_core)] +#![no_core] +#![deny(unused)] + +extern crate minicore; pub trait Trait { extern "C" fn trait_associated(a: usize, b: usize) -> usize; @@ -11,7 +18,7 @@ pub trait Trait { } pub mod normal { - use std::arch::asm; + use minicore::asm; pub extern "C" fn function(a: usize, b: usize) -> usize { //~^ ERROR unused variable: `a` @@ -61,7 +68,7 @@ pub mod normal { } pub mod naked { - use std::arch::naked_asm; + use minicore::naked_asm; #[unsafe(naked)] pub extern "C" fn function(a: usize, b: usize) -> usize { diff --git a/tests/ui/asm/naked-functions/unused.x86_64.stderr b/tests/ui/asm/naked-functions/unused.x86_64.stderr index a41e80fdc50d6..366d338d15b48 100644 --- a/tests/ui/asm/naked-functions/unused.x86_64.stderr +++ b/tests/ui/asm/naked-functions/unused.x86_64.stderr @@ -1,66 +1,66 @@ error: unused variable: `a` - --> $DIR/unused.rs:16:32 + --> $DIR/unused.rs:23:32 | LL | pub extern "C" fn function(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` | note: the lint level is defined here - --> $DIR/unused.rs:5:9 + --> $DIR/unused.rs:11:9 | LL | #![deny(unused)] | ^^^^^^ = note: `#[deny(unused_variables)]` implied by `#[deny(unused)]` error: unused variable: `b` - --> $DIR/unused.rs:16:42 + --> $DIR/unused.rs:23:42 | LL | pub extern "C" fn function(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/unused.rs:27:38 + --> $DIR/unused.rs:34:38 | LL | pub extern "C" fn associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/unused.rs:27:48 + --> $DIR/unused.rs:34:48 | LL | pub extern "C" fn associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/unused.rs:35:41 + --> $DIR/unused.rs:42:41 | LL | pub extern "C" fn method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/unused.rs:35:51 + --> $DIR/unused.rs:42:51 | LL | pub extern "C" fn method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/unused.rs:45:40 + --> $DIR/unused.rs:52:40 | LL | extern "C" fn trait_associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/unused.rs:45:50 + --> $DIR/unused.rs:52:50 | LL | extern "C" fn trait_associated(a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` error: unused variable: `a` - --> $DIR/unused.rs:53:43 + --> $DIR/unused.rs:60:43 | LL | extern "C" fn trait_method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_a` error: unused variable: `b` - --> $DIR/unused.rs:53:53 + --> $DIR/unused.rs:60:53 | LL | extern "C" fn trait_method(&self, a: usize, b: usize) -> usize { | ^ help: if this is intentional, prefix it with an underscore: `_b` From 10dbc1c24924e6e7042cabfdce7f215b3bad9442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 7 Aug 2026 13:54:58 +0200 Subject: [PATCH 097/100] Add branch config for perf. unrolling in bors --- .github/workflows/ci.yml | 5 +++-- src/ci/citool/src/main.rs | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f918e3883d6a..0b0c190a533ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,6 +13,7 @@ on: branches: - automation/bors/auto - automation/bors/try + - automation/bors/try-perf - try-perf pull_request: branches: @@ -34,7 +35,7 @@ concurrency: # We add an exception for try builds (automation/bors/try branch) and unrolled rollup builds # (try-perf), which are all triggered on the same branch, but which should be able to run # concurrently. - group: ${{ github.workflow }}-${{ ((github.ref == 'refs/heads/try-perf' || github.ref == 'refs/heads/automation/bors/try') && github.sha) || github.ref }} + group: ${{ github.workflow }}-${{ ((github.ref == 'refs/heads/try-perf' || github.ref == 'refs/heads/automation/bors/try-perf' || github.ref == 'refs/heads/automation/bors/try') && github.sha) || github.ref }} cancel-in-progress: true env: TOOLSTATE_REPO: "https://github.com/rust-lang-nursery/rust-toolstate" @@ -79,7 +80,7 @@ jobs: # access the environment. # # We only enable the environment for the rust-lang/rust repository, so that CI works on forks. - environment: ${{ ((github.repository == 'rust-lang/rust' && (github.ref == 'refs/heads/try-perf' || github.ref == 'refs/heads/automation/bors/try' || github.ref == 'refs/heads/automation/bors/auto')) && 'bors') || '' }} + environment: ${{ ((github.repository == 'rust-lang/rust' && (github.ref == 'refs/heads/try-perf' || github.ref == 'refs/heads/automation/bors/try' || github.ref == 'refs/heads/automation/bors/try-perf' || github.ref == 'refs/heads/automation/bors/auto')) && 'bors') || '' }} env: CI_JOB_NAME: ${{ matrix.name }} CI_JOB_DOC_URL: ${{ matrix.doc_url }} diff --git a/src/ci/citool/src/main.rs b/src/ci/citool/src/main.rs index 9b9cbe3862e39..8afda476ea68f 100644 --- a/src/ci/citool/src/main.rs +++ b/src/ci/citool/src/main.rs @@ -40,7 +40,9 @@ impl GitHubContext { fn get_run_type(&self) -> Option { match (self.event_name.as_str(), self.branch_ref.as_str()) { ("pull_request", _) => Some(RunType::PullRequest), - ("push", "refs/heads/try-perf") => Some(RunType::TryJob { job_patterns: None }), + ("push", "refs/heads/automation/bors/try-perf" | "refs/heads/try-perf") => { + Some(RunType::TryJob { job_patterns: None }) + } ("push", "refs/heads/automation/bors/try") => { let patterns = self.get_try_job_patterns(); let patterns = if !patterns.is_empty() { Some(patterns) } else { None }; From 1bd2075273fde57559daa0e84211859bbcda84cd Mon Sep 17 00:00:00 2001 From: Augie Fackler Date: Fri, 7 Aug 2026 08:20:42 -0400 Subject: [PATCH 098/100] rustc_codegen_llvm: handle sm_101* features being an alias LLVM 24 moved sm_101{,a,f} features to just be an alias for the matching sm_110 feature. Even though the breaking change in LLVM didn't introduce the 110 flavors, they appear to not exist in older LLVMs so we just gate on LLVM 24. --- compiler/rustc_codegen_llvm/src/llvm_util.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 6892e616e1f11..767da26858e46 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -252,6 +252,12 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option None, s => Some(LLVMFeature::new(s)), }, + Arch::Nvptx64 => match s { + "sm_101" if major >= 24 => Some(LLVMFeature::new("sm_110")), + "sm_101a" if major >= 24 => Some(LLVMFeature::new("sm_110a")), + "sm_101f" if major >= 24 => Some(LLVMFeature::new("sm_110f")), + s => Some(LLVMFeature::new(s)), + }, // Filter out features that are not supported by the current LLVM version Arch::PowerPC | Arch::PowerPC64 => match s { "power8-crypto" => Some(LLVMFeature::new("crypto")), From 6eac4e4d3901c71463235ed6ed25d4e3db48127e Mon Sep 17 00:00:00 2001 From: MarcoIeni <11428655+MarcoIeni@users.noreply.github.com> Date: Fri, 7 Aug 2026 16:19:23 +0200 Subject: [PATCH 099/100] renovate: clarify that vulnerability PRs are opened automatically --- .github/renovate.json5 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 1827901fc041e..390a41c64931b 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -19,8 +19,13 @@ "src/doc/book", "src/doc/reference" ], - // Require manual approval from the Dependency Dashboard before opening PRs + // Require manual approval from the Dependency Dashboard before opening PRs, + // except for the update types explicitly configured below. "dependencyDashboardApproval": true, + // No dashboard approval necessary for security updates + "vulnerabilityAlerts": { + "dependencyDashboardApproval": false + }, // Renovate shouldn't update a PR if it is in the bors merge queue. "stopUpdatingLabel": "S-waiting-on-bors", "packageRules": [ From f231e430bc212fe298420367e18b42b3167c9cfb Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 7 Aug 2026 10:47:29 +1000 Subject: [PATCH 100/100] Streamline `canonicalize_param_env` There are two canonicalization steps done by `canonicalize_input` and `canonicalize_param_env`: `env` (possible cached) and `rest`. `canonicalize_param_env` does the `env` step. It returns several pieces of a canonicalizer (either from the cache or by constructing a canonicalizer) and then `canonicalize_input` uses those parts to construct a second canonicalizer, which it uses for `rest`. This commit changes things so that `canonicalize_param_env` does the `env` part (if necessary) and then returns a canonicalizer that can do the `rest` part. I find this easier to read. In particular, we no longer construct an `env` canonicalizer when it's not necessary, we immediately construct the `rest` canonicalizer. --- .../src/canonical/canonicalizer.rs | 135 +++++++++--------- 1 file changed, 66 insertions(+), 69 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs index 1ebbeaec482e2..73d969a398198 100644 --- a/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs +++ b/compiler/rustc_next_trait_solver/src/canonical/canonicalizer.rs @@ -1,3 +1,5 @@ +use std::collections::hash_map::Entry; + use rustc_type_ir::data_structures::{HashMap, ensure_sufficient_stack}; use rustc_type_ir::inherent::*; use rustc_type_ir::solve::{Goal, QueryInput}; @@ -113,68 +115,80 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { Canonical { max_universe, var_kinds, value } } - fn canonicalize_param_env( - delegate: &'a D, - param_env: I::ParamEnv, - ) -> ( - I::ParamEnv, - ThinVec, - Vec>, - HashMap, - ) { + // The return value is the canonicalized `param_env`, plus a canonicalizer suitable for + // canonicalizing the rest of the input. (For efficiency, and when appropriate, the returned + // canonicalizer will be the same one used on `param_env`, with suitable modifications.) + fn canonicalize_param_env(delegate: &'a D, param_env: I::ParamEnv) -> (I::ParamEnv, Self) { if !param_env.has_type_flags(NEEDS_CANONICAL) { - return (param_env, ThinVec::new(), Vec::new(), Default::default()); + let rest_canonicalizer = Canonicalizer::new( + delegate, + CanonicalizeMode::Input(CanonicalizeInputKind::Predicate), + ); + + return (param_env, rest_canonicalizer); } + // Do the `env` canonicalization, and then convert the canonicalizer to `rest` form for + // subsequent use. + let do_env_and_make_rest = || { + let mut env_canonicalizer = Canonicalizer::new( + delegate, + CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv), + ); + let param_env = param_env.fold_with(&mut env_canonicalizer); + + // We do not reuse the cache as it may contain entries whose canonicalized + // value contains `'static`. While we could alternatively handle this by + // checking for `'static` when using cached entries, this does not + // feel worth the effort. I do not expect that a `ParamEnv` will ever + // contain large enough types for caching to be necessary. + debug_assert!(env_canonicalizer.sub_root_lookup_table.is_empty()); + let rest_canonicalizer = Canonicalizer { + canonicalize_mode: CanonicalizeMode::Input(CanonicalizeInputKind::Predicate), + cache: Default::default(), + ..env_canonicalizer + }; + + (param_env, rest_canonicalizer) + }; + // Check whether we can use the global cache for this param_env. As we only use // the `param_env` itself as the cache key, considering any additional information - // durnig its canonicalization would be incorrect. We always canonicalize region + // during its canonicalization would be incorrect. We always canonicalize region // inference variables in a separate universe, so these are fine. However, we do // track the universe of type and const inference variables so these must not be // globally cached. We don't rely on any additional information when canonicalizing // placeholders. if !param_env.has_non_region_infer() { - delegate.cx().with_canonical_param_env_cache(|cache| { - let entry = cache.0.entry(param_env).or_insert_with(|| { - let mut env_canonicalizer = Canonicalizer::new( + delegate.cx().with_canonical_param_env_cache(|cache| match cache.0.entry(param_env) { + Entry::Vacant(e) => { + // Cache miss. Do `env` canonicalization and get `rest_canonicalizer`, and + // fill in the cache entry. + let (param_env, rest_canonicalizer) = do_env_and_make_rest(); + e.insert(CanonicalParamEnvCacheEntry { + param_env, + variables: rest_canonicalizer.variables.clone(), + var_kinds: rest_canonicalizer.var_kinds.clone(), + variable_lookup_table: rest_canonicalizer.variable_lookup_table.clone(), + }); + (param_env, rest_canonicalizer) + } + Entry::Occupied(e) => { + // Cache hit; no canonicalization required. Just set up `rest_canonicalizer`. + let e = e.get(); + let mut rest_canonicalizer = Canonicalizer::new( delegate, - CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv), + CanonicalizeMode::Input(CanonicalizeInputKind::Predicate), ); - let param_env = param_env.fold_with(&mut env_canonicalizer); - debug_assert!(env_canonicalizer.sub_root_lookup_table.is_empty()); - CanonicalParamEnvCacheEntry { - param_env, - variable_lookup_table: env_canonicalizer.variable_lookup_table, - var_kinds: env_canonicalizer.var_kinds, - variables: env_canonicalizer.variables, - } - }); - - // The obvious thing to do here is `variables.clone()`. But this `new`+`extend` - // combination results in the variables having more spare capacity, which avoids - // some later allocations and makes things a little faster. - let mut variables = ThinVec::new(); - variables.extend(entry.variables.iter().copied()); - ( - entry.param_env, - variables, - entry.var_kinds.clone(), - entry.variable_lookup_table.clone(), - ) + rest_canonicalizer.variables.extend(e.variables.iter().copied()); + rest_canonicalizer.var_kinds.clone_from(&e.var_kinds); + rest_canonicalizer.variable_lookup_table.clone_from(&e.variable_lookup_table); + (e.param_env, rest_canonicalizer) + } }) } else { - let mut env_canonicalizer = Canonicalizer::new( - delegate, - CanonicalizeMode::Input(CanonicalizeInputKind::ParamEnv), - ); - let param_env = param_env.fold_with(&mut env_canonicalizer); - debug_assert!(env_canonicalizer.sub_root_lookup_table.is_empty()); - ( - param_env, - env_canonicalizer.variables, - env_canonicalizer.var_kinds, - env_canonicalizer.variable_lookup_table, - ) + // Do `env` canonicalization and get `rest_canonicalizer`. + do_env_and_make_rest() } } @@ -190,27 +204,10 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { delegate: &'a D, input: QueryInput, ) -> (ThinVec, ty::Canonical>) { - // First canonicalize the `param_env` while keeping `'static` - let (param_env, variables, var_kinds, variable_lookup_table) = - Canonicalizer::canonicalize_param_env(delegate, input.goal.param_env); - - // Then canonicalize the rest of the input without keeping `'static` - // while *mostly* reusing the canonicalizer from above. - // - // We do not reuse the cache as it may contain entries whose canonicalized - // value contains `'static`. While we could alternatively handle this by - // checking for `'static` when using cached entries, this does not - // feel worth the effort. I do not expect that a `ParamEnv` will ever - // contain large enough types for caching to be necessary. - let mut rest_canonicalizer = Canonicalizer { - variables, - variable_lookup_table, - var_kinds, - ..Canonicalizer::new( - delegate, - CanonicalizeMode::Input(CanonicalizeInputKind::Predicate), - ) - }; + // First canonicalize the `param_env` while keeping `'static`. This produces a + // canonicalizer that can canonicalize the rest of the input without keeping `'static`. + let (param_env, mut rest_canonicalizer) = + Self::canonicalize_param_env(delegate, input.goal.param_env); let predicate = input.goal.predicate; let predicate = predicate.fold_with(&mut rest_canonicalizer);