From c6cb702157d44d24f14adfae5e2823f8432e83d3 Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Sat, 13 Sep 2025 19:56:48 -0400 Subject: [PATCH 01/61] constify `From` impls for the `Cow::Borrowed` variant --- library/alloc/src/bstr.rs | 3 ++- library/alloc/src/ffi/c_str.rs | 3 ++- library/alloc/src/string.rs | 3 ++- library/alloc/src/vec/cow.rs | 6 ++++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/library/alloc/src/bstr.rs b/library/alloc/src/bstr.rs index 338c7ac7f8876..ee41fe50c4251 100644 --- a/library/alloc/src/bstr.rs +++ b/library/alloc/src/bstr.rs @@ -598,7 +598,8 @@ impl Clone for Box { } #[unstable(feature = "bstr", issue = "134915")] -impl<'a> From<&'a ByteStr> for Cow<'a, ByteStr> { +#[rustc_const_unstable(feature = "const_convert", issue = "143773")] +impl<'a> const From<&'a ByteStr> for Cow<'a, ByteStr> { #[inline] fn from(s: &'a ByteStr) -> Self { Cow::Borrowed(s) diff --git a/library/alloc/src/ffi/c_str.rs b/library/alloc/src/ffi/c_str.rs index 3e78d680ea68a..8b2ae3f749e91 100644 --- a/library/alloc/src/ffi/c_str.rs +++ b/library/alloc/src/ffi/c_str.rs @@ -878,7 +878,8 @@ impl<'a> From for Cow<'a, CStr> { } #[stable(feature = "cow_from_cstr", since = "1.28.0")] -impl<'a> From<&'a CStr> for Cow<'a, CStr> { +#[rustc_const_unstable(feature = "const_convert", issue = "143773")] +impl<'a> const From<&'a CStr> for Cow<'a, CStr> { /// Converts a [`CStr`] into a borrowed [`Cow`] without copying or allocating. #[inline] fn from(s: &'a CStr) -> Cow<'a, CStr> { diff --git a/library/alloc/src/string.rs b/library/alloc/src/string.rs index ae30cabf5af5b..70a90db8af845 100644 --- a/library/alloc/src/string.rs +++ b/library/alloc/src/string.rs @@ -3096,7 +3096,8 @@ impl<'a> From> for String { #[cfg(not(no_global_oom_handling))] #[stable(feature = "rust1", since = "1.0.0")] -impl<'a> From<&'a str> for Cow<'a, str> { +#[rustc_const_unstable(feature = "const_convert", issue = "143773")] +impl<'a> const From<&'a str> for Cow<'a, str> { /// Converts a string slice into a [`Borrowed`] variant. /// No heap allocation is performed, and the string /// is not copied. diff --git a/library/alloc/src/vec/cow.rs b/library/alloc/src/vec/cow.rs index c18091705a636..69b09d8e2006c 100644 --- a/library/alloc/src/vec/cow.rs +++ b/library/alloc/src/vec/cow.rs @@ -2,7 +2,8 @@ use super::Vec; use crate::borrow::Cow; #[stable(feature = "cow_from_vec", since = "1.8.0")] -impl<'a, T: Clone> From<&'a [T]> for Cow<'a, [T]> { +#[rustc_const_unstable(feature = "const_convert", issue = "143773")] +impl<'a, T: Clone> const From<&'a [T]> for Cow<'a, [T]> { /// Creates a [`Borrowed`] variant of [`Cow`] /// from a slice. /// @@ -15,7 +16,8 @@ impl<'a, T: Clone> From<&'a [T]> for Cow<'a, [T]> { } #[stable(feature = "cow_from_array_ref", since = "1.77.0")] -impl<'a, T: Clone, const N: usize> From<&'a [T; N]> for Cow<'a, [T]> { +#[rustc_const_unstable(feature = "const_convert", issue = "143773")] +impl<'a, T: Clone, const N: usize> const From<&'a [T; N]> for Cow<'a, [T]> { /// Creates a [`Borrowed`] variant of [`Cow`] /// from a reference to an array. /// From 1a43039e4d81477834ed96d640b95fb9e8ee4006 Mon Sep 17 00:00:00 2001 From: ron Date: Tue, 10 Feb 2026 10:21:46 -0500 Subject: [PATCH 02/61] Fix typos and grammar in library documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - library/core/src/macros/panic.md: remove duplicate word ("another other" → "another") - library/core/src/error.md: fix spacing ("Often times" → "Oftentimes") - library/core/src/ptr/docs/as_ref.md: fix wrong types in Null-unchecked section (&mut T → &T) - library/core/src/ffi/c_ulong.md: fix article ("an u64" → "a u64") - library/core/src/ffi/c_void.md: fix plural ("old Rust compiler" → "old Rust compilers") - library/core/src/ptr/docs/as_uninit_slice.md: fix subject-verb agreement ("is true" → "are true") - library/std_detect/README.md: fix article ("an user-space" → "a user-space") --- library/core/src/error.md | 2 +- library/core/src/ffi/c_ulong.md | 2 +- library/core/src/ffi/c_void.md | 2 +- library/core/src/macros/panic.md | 2 +- library/core/src/ptr/docs/as_ref.md | 2 +- library/core/src/ptr/docs/as_uninit_slice.md | 2 +- library/std_detect/README.md | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/library/core/src/error.md b/library/core/src/error.md index 4b62391cafc37..12027a05bab3c 100644 --- a/library/core/src/error.md +++ b/library/core/src/error.md @@ -34,7 +34,7 @@ responsibilities they cover: ## Converting Errors into Panics -The panic and error systems are not entirely distinct. Often times errors +The panic and error systems are not entirely distinct. Oftentimes errors that are anticipated runtime failures in an API might instead represent bugs to a caller. For these situations the standard library provides APIs for constructing panics with an `Error` as its source. diff --git a/library/core/src/ffi/c_ulong.md b/library/core/src/ffi/c_ulong.md index 4ab304e657773..e311231911ba8 100644 --- a/library/core/src/ffi/c_ulong.md +++ b/library/core/src/ffi/c_ulong.md @@ -1,5 +1,5 @@ Equivalent to C's `unsigned long` type. -This type will always be [`u32`] or [`u64`]. Most notably, many Linux-based systems assume an `u64`, but Windows assumes `u32`. The C standard technically only requires that this type be an unsigned integer with the size of a [`long`], although in practice, no system would have a `ulong` that is neither a `u32` nor `u64`. +This type will always be [`u32`] or [`u64`]. Most notably, many Linux-based systems assume a `u64`, but Windows assumes `u32`. The C standard technically only requires that this type be an unsigned integer with the size of a [`long`], although in practice, no system would have a `ulong` that is neither a `u32` nor `u64`. [`long`]: c_long diff --git a/library/core/src/ffi/c_void.md b/library/core/src/ffi/c_void.md index ee7403aa04099..1c3ae6333d827 100644 --- a/library/core/src/ffi/c_void.md +++ b/library/core/src/ffi/c_void.md @@ -9,7 +9,7 @@ stabilized, it is recommended to use a newtype wrapper around an empty byte array. See the [Nomicon] for details. One could use `std::os::raw::c_void` if they want to support old Rust -compiler down to 1.1.0. After Rust 1.30.0, it was re-exported by +compilers down to 1.1.0. After Rust 1.30.0, it was re-exported by this definition. For more information, please read [RFC 2521]. [Nomicon]: https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs diff --git a/library/core/src/macros/panic.md b/library/core/src/macros/panic.md index 6bd23b3072ed9..1a436c7564e0f 100644 --- a/library/core/src/macros/panic.md +++ b/library/core/src/macros/panic.md @@ -19,7 +19,7 @@ call. You can override the panic hook using [`std::panic::set_hook()`]. Inside the hook a panic can be accessed as a `&dyn Any + Send`, which contains either a `&str` or `String` for regular `panic!()` invocations. (Whether a particular invocation contains the payload at type `&str` or `String` is unspecified and can change.) -To panic with a value of another other type, [`panic_any`] can be used. +To panic with a value of another type, [`panic_any`] can be used. See also the macro [`compile_error!`], for raising errors during compilation. diff --git a/library/core/src/ptr/docs/as_ref.md b/library/core/src/ptr/docs/as_ref.md index 2c7d6e149b76a..4b41031198236 100644 --- a/library/core/src/ptr/docs/as_ref.md +++ b/library/core/src/ptr/docs/as_ref.md @@ -16,4 +16,4 @@ determined to be null or not. See [`is_null`] for more information. # Null-unchecked version If you are sure the pointer can never be null, you can use `as_ref_unchecked` which returns -`&mut T` instead of `Option<&mut T>`. +`&T` instead of `Option<&T>`. diff --git a/library/core/src/ptr/docs/as_uninit_slice.md b/library/core/src/ptr/docs/as_uninit_slice.md index 1113f4748c2df..a2402b020468f 100644 --- a/library/core/src/ptr/docs/as_uninit_slice.md +++ b/library/core/src/ptr/docs/as_uninit_slice.md @@ -7,7 +7,7 @@ that the value has to be initialized. # Safety When calling this method, you have to ensure that *either* the pointer is null *or* -all of the following is true: +all of the following are true: * The pointer must be [valid] for reads for `ptr.len() * size_of::()` many bytes, and it must be properly aligned. This means in particular: diff --git a/library/std_detect/README.md b/library/std_detect/README.md index 895f3426d0495..8f94d3069c6a9 100644 --- a/library/std_detect/README.md +++ b/library/std_detect/README.md @@ -25,7 +25,7 @@ regard to better serve the needs of `#[no_std]` targets. * All `x86`/`x86_64` targets are supported on all platforms by querying the `cpuid` instruction directly for the features supported by the hardware and - the operating system. `std_detect` assumes that the binary is an user-space + the operating system. `std_detect` assumes that the binary is a user-space application. * Linux/Android: From 3ffc0fbdd2e666bc2658a95a9074c317eb651ba4 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Thu, 2 Jul 2026 13:45:05 -0700 Subject: [PATCH 03/61] rustdoc: warn on improperly interleaved HTML/MD --- src/librustdoc/passes/lint/html_tags.rs | 276 +++++++++++++++--- .../rustdoc-ui/lints/invalid-html-tags.stderr | 12 +- 2 files changed, 243 insertions(+), 45 deletions(-) diff --git a/src/librustdoc/passes/lint/html_tags.rs b/src/librustdoc/passes/lint/html_tags.rs index 86b8e7b6f86aa..5b4557580eda6 100644 --- a/src/librustdoc/passes/lint/html_tags.rs +++ b/src/librustdoc/passes/lint/html_tags.rs @@ -16,7 +16,7 @@ use crate::html::markdown::main_body_opts; pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) { let tcx = cx.tcx; - let report_diag = |msg: String, range: &Range, is_open_tag: bool| { + let report_diag = |msg: String, range: &Range, mode: HtmlDiagMode| { let sp = match source_span_for_markdown_range(tcx, dox, range, &item.attrs.doc_strings) { Some((sp, _)) => sp, None => item.attr_span(tcx), @@ -34,7 +34,7 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & // We don't try to detect stuff `` because that's not valid HTML, // and we don't try to detect stuff `` because that's not valid Rust. let mut generics_end = range.end; - if is_open_tag + if mode == HtmlDiagMode::Unclosed && dox[..generics_end].ends_with('>') && let Some(mut generics_start) = extract_path_backwards(dox, range.start) { @@ -103,6 +103,48 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & ], Applicability::MaybeIncorrect, ); + } else if let HtmlDiagMode::Unopened { possible_pair: Some(possible_pair) } = mode { + let (reason_display_name, reason_range) = match possible_pair.reason { + HtmlOrMarkdownTag::Markdown( + tag @ (TagEnd::Paragraph | TagEnd::Table), + range, + ) if dox.as_bytes().get(range.end) == Some(&b'>') => ( + format!( + "Markdown {}, which is interrupted by a block quote", + markdown_tag_name(tag) + ), + range, + ), + HtmlOrMarkdownTag::Markdown(tag, range) => { + (format!("Markdown {}", markdown_tag_name(tag)), range) + } + HtmlOrMarkdownTag::Html(name, range) => (format!("HTML `{name}`"), range), + }; + if let HtmlOrMarkdownTag::Html(_, unclosed_tag_range) = + possible_pair.unclosed_tag + && let Some((unclosed_tag_span, _)) = source_span_for_markdown_range( + tcx, + dox, + &unclosed_tag_range, + &item.attrs.doc_strings, + ) + { + lint.span_label( + unclosed_tag_span, + format!("does not match this unclosed tag"), + ); + } + if let Some((reason_span, _)) = source_span_for_markdown_range( + tcx, + dox, + &reason_range, + &item.attrs.doc_strings, + ) { + lint.span_label( + reason_span, + format!("because of this {reason_display_name}"), + ); + } } }), ); @@ -152,33 +194,61 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & Event::Html(text) | Event::InlineHtml(text) if !in_code_block => { tagp.extract_tags(&text, range, &mut is_in_comment, &report_diag) } + Event::Start(Tag::HtmlBlock) | Event::End(TagEnd::HtmlBlock) => {} + Event::Start(tag) if !in_code_block => { + tagp.push_markdown_tag(tag.into(), range); + } + Event::End(tag) if !in_code_block => { + tagp.pop_markdown_tag(tag, range, &report_diag); + } Event::End(TagEnd::CodeBlock) => in_code_block = false, _ => {} } } if let Some(range) = is_in_comment { - report_diag("Unclosed HTML comment".to_string(), &range, false); + report_diag("Unclosed HTML comment".to_string(), &range, HtmlDiagMode::Incomplete); } else if let &Some(quote_pos) = &tagp.quote_pos { let qr = Range { start: quote_pos, end: quote_pos }; report_diag( format!("unclosed quoted HTML attribute on tag `{}`", &tagp.tag_name), &qr, - false, + HtmlDiagMode::Incomplete, ); } else { if !tagp.tag_name.is_empty() { report_diag( format!("incomplete HTML tag `{}`", &tagp.tag_name), &(tagp.tag_start_pos..dox.len()), - false, + HtmlDiagMode::Incomplete, ); } - for (tag, range) in tagp.tags.iter().filter(|(t, _)| { - let t = t.to_lowercase(); - !is_implicitly_self_closing(&t) - }) { - report_diag(format!("unclosed HTML tag `{tag}`"), range, true); + for tag in tagp.tags.iter().chain( + tagp.unclosed_tag_buf + .iter() + .map(|buffered_unclosed_tag| &buffered_unclosed_tag.unclosed_tag), + ) { + match tag { + HtmlOrMarkdownTag::Html(tag, range) => { + if !is_implicitly_self_closing(&tag.to_lowercase()) { + report_diag( + format!("unclosed HTML tag `{tag}`"), + range, + HtmlDiagMode::Unclosed, + ); + } + } + HtmlOrMarkdownTag::Markdown(tag, range) => { + report_diag( + format!( + "invalid tree with Markdown delimiter `{tag_name}`", + tag_name = markdown_tag_name(*tag) + ), + range, + HtmlDiagMode::Unclosed, + ); + } + } } } } @@ -254,10 +324,30 @@ fn is_valid_for_html_tag_name(c: char, is_empty: bool) -> bool { c.is_ascii_alphabetic() || !is_empty && (c == '-' || c.is_ascii_digit()) } +#[derive(Eq, PartialEq, Debug, Clone)] +enum HtmlOrMarkdownTag { + Html(String, Range), + Markdown(TagEnd, Range), +} + +#[derive(Eq, PartialEq, Debug, Clone)] +struct BufferedUnclosedTag { + unclosed_tag: HtmlOrMarkdownTag, + reason: HtmlOrMarkdownTag, +} + +#[derive(Eq, PartialEq, Debug, Clone)] +enum HtmlDiagMode { + Unclosed, + Unopened { possible_pair: Option }, + Incomplete, +} + /// Parse html tags to ensure they are well-formed #[derive(Debug, Clone)] struct TagParser { - tags: Vec<(String, Range)>, + tags: Vec, + unclosed_tag_buf: Vec, /// Name of the tag that is being parsed, if we are within a tag. /// /// Since the `<` and name of a tag must appear on the same line with no whitespace, @@ -279,6 +369,7 @@ impl TagParser { fn new() -> Self { Self { tags: Vec::new(), + unclosed_tag_buf: Vec::new(), tag_name: String::with_capacity(8), tag_start_pos: 0, is_closing: false, @@ -289,34 +380,57 @@ impl TagParser { } } - fn drop_tag(&mut self, range: Range, f: &impl Fn(String, &Range, bool)) { + fn drop_tag(&mut self, range: Range, f: &impl Fn(String, &Range, HtmlDiagMode)) { let tag_name_low = self.tag_name.to_lowercase(); - if let Some(pos) = self.tags.iter().rposition(|(t, _)| t.to_lowercase() == tag_name_low) { + let tag_name_is_match = |tag: &HtmlOrMarkdownTag| match tag { + HtmlOrMarkdownTag::Html(name, _span) => name.to_lowercase() == tag_name_low, + HtmlOrMarkdownTag::Markdown(..) => false, + }; + if let Some(pos) = self.tags.iter().rposition(tag_name_is_match) { // If the tag is nested inside a "` (the `h2` tag isn't required // but it helps for the visualization). - f(format!("unopened HTML tag `{}`", &self.tag_name), &range, false); + let mode = HtmlDiagMode::Unopened { + possible_pair: self + .unclosed_tag_buf + .iter() + .rposition(|buf| tag_name_is_match(&buf.unclosed_tag)) + .map(|pos| self.unclosed_tag_buf.remove(pos)), + }; + f(format!("unopened HTML tag `{}`", &self.tag_name), &range, mode); } } @@ -325,7 +439,7 @@ impl TagParser { &mut self, range: Range, lt_pos: usize, - f: &impl Fn(String, &Range, bool), + f: &impl Fn(String, &Range, HtmlDiagMode), ) { let global_pos = range.start + lt_pos; // is this check needed? @@ -337,7 +451,7 @@ impl TagParser { f( format!("incomplete HTML tag `{}`", &self.tag_name), &(self.tag_start_pos..global_pos), - false, + HtmlDiagMode::Incomplete, ); self.tag_parsed(); } @@ -348,7 +462,7 @@ impl TagParser { range: &Range, start_pos: usize, iter: &mut Peekable>, - f: &impl Fn(String, &Range, bool), + f: &impl Fn(String, &Range, HtmlDiagMode), ) { let mut prev_pos = start_pos; @@ -419,7 +533,7 @@ impl TagParser { pos: usize, c: char, iter: &mut Peekable>, - f: &impl Fn(String, &Range, bool), + f: &impl Fn(String, &Range, HtmlDiagMode), ) { // we can store this as a local, since html5 does require the `/` and `>` // to not be separated by whitespace. @@ -469,15 +583,22 @@ impl TagParser { if is_self_closing { // https://html.spec.whatwg.org/#parse-error-non-void-html-element-start-tag-with-trailing-solidus let valid = ALLOWED_UNCLOSED.contains(&&self.tag_name[..]) - || self.tags.iter().take(pos + 1).any(|(at, _)| { - let at = at.to_lowercase(); - at == "svg" || at == "math" + || self.tags.iter().take(pos + 1).any(|tag| match tag { + HtmlOrMarkdownTag::Html(at, _) => { + let at = at.to_lowercase(); + at == "svg" || at == "math" + } + HtmlOrMarkdownTag::Markdown(..) => false, }); if !valid { - f(format!("invalid self-closing HTML tag `{}`", self.tag_name), &r, false); + f( + format!("invalid self-closing HTML tag `{}`", self.tag_name), + &r, + HtmlDiagMode::Incomplete, + ); } } else if !self.tag_name.is_empty() { - self.tags.push((std::mem::take(&mut self.tag_name), r)); + self.tags.push(HtmlOrMarkdownTag::Html(std::mem::take(&mut self.tag_name), r)); } self.tag_parsed(); } @@ -493,7 +614,7 @@ impl TagParser { text: &str, range: Range, is_in_comment: &mut Option>, - f: &impl Fn(String, &Range, bool), + f: &impl Fn(String, &Range, HtmlDiagMode), ) { let mut iter = text.char_indices().peekable(); let mut prev_pos = 0; @@ -540,6 +661,83 @@ impl TagParser { } } } + + fn push_markdown_tag(&mut self, tag: TagEnd, range: Range) { + self.tags.push(HtmlOrMarkdownTag::Markdown(tag, range)); + } + + fn pop_markdown_tag( + &mut self, + tag_end: TagEnd, + range: Range, + f: &impl Fn(String, &Range, HtmlDiagMode), + ) { + let tag_is_match = |tag: &HtmlOrMarkdownTag| match tag { + HtmlOrMarkdownTag::Html(..) => false, + HtmlOrMarkdownTag::Markdown(last_tag, _span) => *last_tag == tag_end, + }; + if let Some(pos) = self.tags.iter().rposition(tag_is_match) { + // If the tag is nested inside a "` (the `h2` tag isn't required + // but it helps for the visualization). + let mode = HtmlDiagMode::Unopened { + possible_pair: self + .unclosed_tag_buf + .iter() + .rposition(|buf| tag_is_match(&buf.unclosed_tag)) + .map(|pos| self.unclosed_tag_buf.remove(pos)), + }; + f(format!("improperly nested Markdown {}", markdown_tag_name(tag_end)), &range, mode); + } + } +} + +fn markdown_tag_name(tag: TagEnd) -> &'static str { + match tag { + TagEnd::Paragraph => "paragraph", + TagEnd::Heading(..) => "heading", + TagEnd::BlockQuote => "block quote `>`", + TagEnd::CodeBlock => "code block", + TagEnd::HtmlBlock => "html", + TagEnd::List(true) => "numbered list", + TagEnd::List(false) => "bullet list", + TagEnd::Item => "list item", + TagEnd::FootnoteDefinition => "footnote definition", + TagEnd::Table => "table", + TagEnd::TableHead => "table head", + TagEnd::TableRow => "table row", + TagEnd::TableCell => "table cell", + TagEnd::Emphasis => "emphasis", + TagEnd::Strong => "strong emphasis", + TagEnd::Strikethrough => "strikethrough", + TagEnd::Link => "link", + TagEnd::Image => "image", + TagEnd::MetadataBlock(..) => "front matter", + } } #[cfg(test)] diff --git a/tests/rustdoc-ui/lints/invalid-html-tags.stderr b/tests/rustdoc-ui/lints/invalid-html-tags.stderr index b6ec22c247901..4b113457f58ce 100644 --- a/tests/rustdoc-ui/lints/invalid-html-tags.stderr +++ b/tests/rustdoc-ui/lints/invalid-html-tags.stderr @@ -28,6 +28,12 @@ error: unclosed HTML tag `script` LL | /// +pub fn no_error_8() {} + +/// +pub fn no_error_9() {} + +/// +pub fn no_error_10() {} + +/// The Markdown parser takes care of its own syntax +/// +/// *testing __one two* three__ +pub fn no_error_11() {} + +///
Example +/// +/// ```console +/// error +/// ``` +/// +///
+pub fn no_error_12() {} + +/// Figure 1: an X +/// +/// +/// +//~^ ERROR unopened HTML tag `svg` +//~^^^^^^^ NOTE does not match this unclosed tag +//~^^^^^^^^^ NOTE because of this Markdown paragraph +pub struct Diagram; diff --git a/tests/rustdoc-ui/lints/invalid-html-tags.stderr b/tests/rustdoc-ui/lints/invalid-html-tags.stderr index 4b113457f58ce..dd7f1425d9910 100644 --- a/tests/rustdoc-ui/lints/invalid-html-tags.stderr +++ b/tests/rustdoc-ui/lints/invalid-html-tags.stderr @@ -1,5 +1,5 @@ error: unclosed HTML tag `p` - --> $DIR/invalid-html-tags.rs:3:5 + --> $DIR/invalid-html-tags.rs:4:5 | LL | //!

💩

| ^^^ @@ -11,115 +11,115 @@ LL | #![deny(rustdoc::invalid_html_tags)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: unclosed HTML tag `p` - --> $DIR/invalid-html-tags.rs:3:9 + --> $DIR/invalid-html-tags.rs:4:9 | LL | //!

💩

| ^^^ error: unclosed HTML tag `unknown` - --> $DIR/invalid-html-tags.rs:11:5 + --> $DIR/invalid-html-tags.rs:12:5 | LL | /// | ^^^^^^^^^ error: unclosed HTML tag `script` - --> $DIR/invalid-html-tags.rs:14:5 + --> $DIR/invalid-html-tags.rs:15:5 | LL | /// ` (the `h2` tag isn't required @@ -664,7 +735,30 @@ impl TagParser { } } - fn push_markdown_tag(&mut self, tag: TagEnd, range: Range) { + fn push_markdown_tag( + &mut self, + tag: TagEnd, + range: Range, + f: &impl Fn(String, &Range, HtmlDiagMode), + ) { + // If the tag is nested inside a " d* + self.tags.push(HtmlOrMarkdownTag::Html(tag.name().to_owned(), tag_range)); } else { // `tags` is used as a queue, meaning that everything after `pos` is included inside it. // So `**` will look like `["*", "span"]`. So when closing `*`, we will still diff --git a/tests/rustdoc-ui/lints/invalid-html-tags-correct-script-style.fixed b/tests/rustdoc-ui/lints/invalid-html-tags-correct-script-style.fixed new file mode 100644 index 0000000000000..a44a00106097f --- /dev/null +++ b/tests/rustdoc-ui/lints/invalid-html-tags-correct-script-style.fixed @@ -0,0 +1,30 @@ +//@ run-rustfix +#![deny(rustdoc::invalid_html_tags)] +//~^ NOTE the lint level is defined here + +//! Warn on Markdown text written in RAWTEXT HTML tags, +//! like *this: +//! +//~^ ERROR nested Markdown emphasis in HTML `script` tag +//~| NOTE Markdown translates this into HTML, but the browser parses it as JavaScript +//~| HELP to turn off Markdown parsing, put the tag at the start of the line +//! +//! Or +//! this* +//~^ ERROR nested Markdown emphasis in HTML `script` tag +//~| NOTE Markdown translates this into HTML, but the browser parses it as JavaScript +//~| HELP to turn off Markdown parsing, put the tag at the start of the line + +/// One +/// +//~^ ERROR nested Markdown emphasis in HTML `script` tag +//~| NOTE Markdown translates this into HTML, but the browser parses it as JavaScript +//~| HELP to turn off Markdown parsing, put the tag at the start of the line +pub struct ScriptMarkdown; + +/// One +/// +//~^ ERROR nested Markdown emphasis in HTML `style` tag +//~| NOTE Markdown translates this into HTML, but the browser parses it as CSS +//~| HELP to turn off Markdown parsing, put the tag at the start of the line +pub struct StyleMarkdown; diff --git a/tests/rustdoc-ui/lints/invalid-html-tags-correct-script-style.rs b/tests/rustdoc-ui/lints/invalid-html-tags-correct-script-style.rs new file mode 100644 index 0000000000000..372612cbb683b --- /dev/null +++ b/tests/rustdoc-ui/lints/invalid-html-tags-correct-script-style.rs @@ -0,0 +1,26 @@ +//@ run-rustfix +#![deny(rustdoc::invalid_html_tags)] +//~^ NOTE the lint level is defined here + +//! Warn on Markdown text written in RAWTEXT HTML tags, +//! like *this: +//~^ ERROR nested Markdown emphasis in HTML `script` tag +//~| NOTE Markdown translates this into HTML, but the browser parses it as JavaScript +//~| HELP to turn off Markdown parsing, put the tag at the start of the line +//! +//! Or this* +//~^ ERROR nested Markdown emphasis in HTML `script` tag +//~| NOTE Markdown translates this into HTML, but the browser parses it as JavaScript +//~| HELP to turn off Markdown parsing, put the tag at the start of the line + +/// One +//~^ ERROR nested Markdown emphasis in HTML `script` tag +//~| NOTE Markdown translates this into HTML, but the browser parses it as JavaScript +//~| HELP to turn off Markdown parsing, put the tag at the start of the line +pub struct ScriptMarkdown; + +/// One +//~^ ERROR nested Markdown emphasis in HTML `style` tag +//~| NOTE Markdown translates this into HTML, but the browser parses it as CSS +//~| HELP to turn off Markdown parsing, put the tag at the start of the line +pub struct StyleMarkdown; diff --git a/tests/rustdoc-ui/lints/invalid-html-tags-correct-script-style.stderr b/tests/rustdoc-ui/lints/invalid-html-tags-correct-script-style.stderr new file mode 100644 index 0000000000000..4bd19ee60ef1e --- /dev/null +++ b/tests/rustdoc-ui/lints/invalid-html-tags-correct-script-style.stderr @@ -0,0 +1,55 @@ +error: improperly nested Markdown emphasis in HTML `script` tag + --> $DIR/invalid-html-tags-correct-script-style.rs:6:10 + | +LL | //! like *this: + | ^^^^^^^^^^^^^^^^^ Markdown translates this into HTML, but the browser parses it as JavaScript + | +note: the lint level is defined here + --> $DIR/invalid-html-tags-correct-script-style.rs:2:9 + | +LL | #![deny(rustdoc::invalid_html_tags)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: to turn off Markdown parsing, put the tag at the start of the line + | +LL ~ //! like *this: +LL ~ //! + | + +error: nested Markdown emphasis in HTML `script` tag + --> $DIR/invalid-html-tags-correct-script-style.rs:11:17 + | +LL | //! Or this* + | ^^^^^^^^^^^^^^^^^ Markdown translates this into HTML, but the browser parses it as JavaScript + | +help: to turn off Markdown parsing, put the tag at the start of the line + | +LL ~ //! Or +LL ~ //! this* + | + +error: nested Markdown emphasis in HTML `script` tag + --> $DIR/invalid-html-tags-correct-script-style.rs:16:17 + | +LL | /// One + | ^^^^^^ Markdown translates this into HTML, but the browser parses it as JavaScript + | +help: to turn off Markdown parsing, put the tag at the start of the line + | +LL ~ /// One +LL ~ /// + | + +error: nested Markdown emphasis in HTML `style` tag + --> $DIR/invalid-html-tags-correct-script-style.rs:22:16 + | +LL | /// One + | ^^^^^^ Markdown translates this into HTML, but the browser parses it as CSS + | +help: to turn off Markdown parsing, put the tag at the start of the line + | +LL ~ /// One +LL ~ /// + | + +error: aborting due to 4 previous errors + diff --git a/tests/rustdoc-ui/lints/invalid-html-tags.rs b/tests/rustdoc-ui/lints/invalid-html-tags.rs index 1616711b0e416..199a015293fc5 100644 --- a/tests/rustdoc-ui/lints/invalid-html-tags.rs +++ b/tests/rustdoc-ui/lints/invalid-html-tags.rs @@ -250,14 +250,14 @@ pub fn no_error_8() {} /// pub fn no_error_9() {} /// pub fn no_error_10() {} From 1e452c3b929f923cb5d657af63c3ebdc4fcd3f15 Mon Sep 17 00:00:00 2001 From: Paul Mabileau Date: Fri, 17 Jul 2026 12:18:45 +0200 Subject: [PATCH 13/61] Fix(lib/fs/win): Fall back on Win32 delete for Dir::remove_file The `test_dir_remove_file` test currently fails under Windows 7 on: ``` thread 'fs::tests::test_dir_remove_file' (2028) panicked at library/std/src/fs/tests.rs:2590:5: dir.remove_file("foo.txt") failed with: The parameter is incorrect. (os error 87) ``` Looking into it, this is because the `FILE_DISPOSITION_INFO_EX` variant of the `SetFileInformationByHandle` API is used while it is only available starting from Windows 10 1607. However, `FILE_DISPOSITION_INFO` is still available since Vista. This is therefore fixed by introducing a fallback to the latter API if the former fails. This is achieved by re-using some logic that is already available through `File::delete` and that does exactly this. Signed-off-by: Paul Mabileau --- library/std/src/sys/fs/windows/dir.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/library/std/src/sys/fs/windows/dir.rs b/library/std/src/sys/fs/windows/dir.rs index cd86f76bbcf83..5e69515b66599 100644 --- a/library/std/src/sys/fs/windows/dir.rs +++ b/library/std/src/sys/fs/windows/dir.rs @@ -6,7 +6,7 @@ use crate::os::windows::io::{ OwnedHandle, RawHandle, }; use crate::path::Path; -use crate::sys::api::{self, SetFileInformation, UnicodeStrRef, WinError}; +use crate::sys::api::{UnicodeStrRef, WinError}; use crate::sys::fs::windows::debug_path_handle; use crate::sys::fs::{File, FileAttr, OpenOptions}; use crate::sys::handle::Handle; @@ -127,16 +127,7 @@ impl Dir { let mut opts = OpenOptions::new(); opts.access_mode(c::DELETE); let handle = self.open_file_native(path, &opts, dir)?; - let info = c::FILE_DISPOSITION_INFO_EX { Flags: c::FILE_DISPOSITION_FLAG_DELETE }; - let result = unsafe { - c::SetFileInformationByHandle( - handle.as_raw_handle(), - c::FileDispositionInfoEx, - (&info).as_ptr(), - size_of::() as _, - ) - }; - if result == 0 { Err(api::get_last_error()).io_result() } else { Ok(()) } + File::from_inner(handle).delete().io_result() } fn rename_native(&self, from: &[u16], to_dir: &Self, to: &[u16], dir: bool) -> io::Result<()> { From 5c88a1c75a7bc1bc45a7adc834dd29478e2c9017 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Thu, 16 Jul 2026 23:05:33 +0000 Subject: [PATCH 14/61] [rpub] Replace Span::caller_location by Body's one Add a method Body::caller_location that resolves the correct caller location constant for a call to a #[track_caller] function, walking inlined source scopes to handle MIR inlining correctly. Body now stores source scope data as a public field. Body::new populates it with empty entries (no inlining info); bodies obtained from the compiler have full scope data populated. Span::as_caller_location is now pub(crate) since users should use Body::caller_location instead, which handles inlining correctly. --- compiler/rustc_public/src/mir/body.rs | 96 +++++++++++-- compiler/rustc_public/src/mir/visit.rs | 10 +- compiler/rustc_public/src/ty.rs | 5 +- .../src/unstable/convert/stable/mir.rs | 36 +++-- .../rustc_public/check_track_caller.rs | 131 +++++++++++------- 5 files changed, 202 insertions(+), 76 deletions(-) diff --git a/compiler/rustc_public/src/mir/body.rs b/compiler/rustc_public/src/mir/body.rs index 20682de5f4825..def6c837a2b85 100644 --- a/compiler/rustc_public/src/mir/body.rs +++ b/compiler/rustc_public/src/mir/body.rs @@ -20,10 +20,10 @@ pub struct Body { /// The first local is the return value pointer, followed by `arg_count` /// locals for the function arguments, followed by any user-declared /// variables and temporaries. - pub(super) locals: LocalDecls, + pub(crate) locals: LocalDecls, /// The number of arguments this function takes. - pub(super) arg_count: usize, + pub(crate) arg_count: usize, /// Debug information pertaining to user variables, including captures. pub var_debug_info: Vec, @@ -31,19 +31,29 @@ pub struct Body { /// Mark an argument (which must be a tuple) as getting passed as its individual components. /// /// This is used for the "rust-call" ABI such as closures. - pub(super) spread_arg: Option, + pub(crate) spread_arg: Option, /// The span that covers the entire function body. pub span: Span, + + /// Source scope information, used by [`Body::caller_location`] for inline-aware resolution. + /// + /// Invariants: + /// - All scope indices referenced by terminators and statements must be within bounds. + /// - `inlined_parent_scope` links must not form cycles. + pub(crate) source_scopes: Vec, } pub type BasicBlockIdx = usize; impl Body { - /// Constructs a `Body`. + /// Constructs a `Body` without inlining information. + /// + /// # Warning /// - /// A constructor is required to build a `Body` from outside the crate - /// because the `arg_count` and `locals` fields are private. + /// This constructor does not accept source scope data today. + /// [`Body::caller_location`] will fall back to the terminator's span, + /// which may be incorrect when MIR inlining is involved. pub fn new( blocks: Vec, locals: LocalDecls, @@ -52,13 +62,15 @@ impl Body { spread_arg: Option, span: Span, ) -> Self { - // If locals doesn't contain enough entries, it can lead to panics in - // `ret_local`, `arg_locals`, and `inner_locals`. assert!( locals.len() > arg_count, "A Body must contain at least a local for the return value and each of the function's arguments" ); - Self { blocks, locals, arg_count, var_debug_info, spread_arg, span } + let source_scopes = vec![ + SourceScopeInfo { inlined: None, inlined_parent_scope: None }; + max_scope(&blocks) as usize + 1 + ]; + Self { blocks, locals, arg_count, var_debug_info, spread_arg, span, source_scopes } } /// Return local that holds this function's return value. @@ -119,6 +131,44 @@ impl Body { pub fn spread_arg(&self) -> Option { self.spread_arg } + + /// Resolve the caller location for a call to a `#[track_caller]` function. + /// + /// Use this when generating the implicit `&'static Location<'static>` argument + /// for a call where [`Instance::requires_caller_location`] is true. + /// + /// Pass `inherited_location` if this body belongs to a `#[track_caller]` function + /// (the implicit parameter it received). Pass `None` otherwise. + /// + /// This method accounts for MIR inlining: when inlined `#[track_caller]` functions + /// are present, the terminator's span may not be the correct location. The method + /// walks the inlined scopes to resolve the right one. + /// + /// [`Instance::requires_caller_location`]: crate::mir::mono::Instance::requires_caller_location + pub fn caller_location( + &self, + terminator: &Terminator, + inherited_location: Option, + ) -> MirConst { + let mut span = terminator.source_info.span; + let mut scope = terminator.source_info.scope; + + while let Some(scope_data) = self.source_scopes.get(scope as usize) { + if let Some((track_caller, callsite_span)) = &scope_data.inlined { + if !track_caller { + return span.as_caller_location(); + } + span = *callsite_span; + } + + match scope_data.inlined_parent_scope { + Some(parent) => scope = parent, + None => break, + } + } + + inherited_location.unwrap_or_else(|| span.as_caller_location()) + } } type LocalDecls = Vec; @@ -748,6 +798,22 @@ impl VarDebugInfo { pub type SourceScope = u32; +/// Data about a source scope, used for caller location resolution. +/// +/// Each entry corresponds to a source scope in the MIR body. Most scopes have no +/// inlined data. For scopes introduced by MIR inlining, `inlined` records whether +/// the inlined callee is `#[track_caller]` and the span of the call site. +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] +pub(crate) struct SourceScopeInfo { + /// Present when this scope was introduced by inlining a function. + /// The `bool` is `true` if the inlined callee is `#[track_caller]`. + /// The `Span` is the call site where inlining occurred. + pub inlined: Option<(bool, Span)>, + /// Nearest (transitive) parent scope that was itself inlined. + /// Skips over intermediate scopes within the same inlined function body. + pub inlined_parent_scope: Option, +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize)] pub struct SourceInfo { pub span: Span, @@ -1103,3 +1169,15 @@ impl ProjectionElem { Ok(deref_ty.ty) } } + +/// Return the maximum scope index referenced by any terminator or statement in `blocks`. +fn max_scope(blocks: &[BasicBlock]) -> u32 { + blocks + .iter() + .flat_map(|bb| { + std::iter::once(bb.terminator.source_info.scope) + .chain(bb.statements.iter().map(|s| s.source_info.scope)) + }) + .max() + .unwrap_or(0) +} diff --git a/compiler/rustc_public/src/mir/visit.rs b/compiler/rustc_public/src/mir/visit.rs index 8cb3237bc4c83..d80c769cd8169 100644 --- a/compiler/rustc_public/src/mir/visit.rs +++ b/compiler/rustc_public/src/mir/visit.rs @@ -408,7 +408,15 @@ macro_rules! super_body { }; ($self:ident, $body:ident, ) => { - let Body { blocks, locals: _, arg_count, var_debug_info, spread_arg: _, span } = $body; + let Body { + blocks, + locals: _, + arg_count, + var_debug_info, + spread_arg: _, + span, + source_scopes: _, + } = $body; for bb in blocks { $self.visit_basic_block(bb); diff --git a/compiler/rustc_public/src/ty.rs b/compiler/rustc_public/src/ty.rs index ada78c557e265..359a4ab62b185 100644 --- a/compiler/rustc_public/src/ty.rs +++ b/compiler/rustc_public/src/ty.rs @@ -289,10 +289,7 @@ impl Span { } /// Create a `&'static core::panic::Location<'static>` constant from this span. - /// - /// This is the value that must be passed as the implicit extra argument - /// when calling a `#[track_caller]` function. - pub fn as_caller_location(&self) -> MirConst { + pub(crate) fn as_caller_location(&self) -> MirConst { with(|c| c.span_as_caller_location(*self)) } } diff --git a/compiler/rustc_public/src/unstable/convert/stable/mir.rs b/compiler/rustc_public/src/unstable/convert/stable/mir.rs index 4293d1bede421..6471b106ae757 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/mir.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/mir.rs @@ -7,7 +7,9 @@ use rustc_public_bridge::{Tables, bridge}; use crate::compiler_interface::BridgeTys; use crate::mir::alloc::GlobalAlloc; -use crate::mir::{ConstOperand, Statement, UserTypeProjection, VarDebugInfoFragment}; +use crate::mir::{ + ConstOperand, SourceScopeInfo, Statement, UserTypeProjection, VarDebugInfoFragment, +}; use crate::ty::{Allocation, ConstantKind, MirConst}; use crate::unstable::Stable; use crate::{Error, alloc, opaque}; @@ -20,8 +22,9 @@ impl<'tcx> Stable<'tcx> for mir::Body<'tcx> { tables: &mut Tables<'cx, BridgeTys>, cx: &CompilerCtxt<'cx, BridgeTys>, ) -> Self::T { - crate::mir::Body::new( - self.basic_blocks + crate::mir::Body { + blocks: self + .basic_blocks .iter() .map(|block| crate::mir::BasicBlock { terminator: block.terminator().stable(tables, cx), @@ -32,7 +35,8 @@ impl<'tcx> Stable<'tcx> for mir::Body<'tcx> { .collect(), }) .collect(), - self.local_decls + locals: self + .local_decls .iter() .map(|decl| crate::mir::LocalDecl { ty: decl.ty.stable(tables, cx), @@ -40,11 +44,25 @@ impl<'tcx> Stable<'tcx> for mir::Body<'tcx> { mutability: decl.mutability.stable(tables, cx), }) .collect(), - self.arg_count, - self.var_debug_info.iter().map(|info| info.stable(tables, cx)).collect(), - self.spread_arg.stable(tables, cx), - self.span.stable(tables, cx), - ) + arg_count: self.arg_count, + var_debug_info: self + .var_debug_info + .iter() + .map(|info| info.stable(tables, cx)) + .collect(), + spread_arg: self.spread_arg.stable(tables, cx), + span: self.span.stable(tables, cx), + source_scopes: self + .source_scopes + .iter() + .map(|scope_data| SourceScopeInfo { + inlined: scope_data.inlined.map(|(instance, span)| { + (instance.def.requires_caller_location(cx.tcx), span.stable(tables, cx)) + }), + inlined_parent_scope: scope_data.inlined_parent_scope.map(|s| s.as_u32()), + }) + .collect(), + } } } diff --git a/tests/ui-fulldeps/rustc_public/check_track_caller.rs b/tests/ui-fulldeps/rustc_public/check_track_caller.rs index 8da55f1129d33..a9d396a479fa5 100644 --- a/tests/ui-fulldeps/rustc_public/check_track_caller.rs +++ b/tests/ui-fulldeps/rustc_public/check_track_caller.rs @@ -1,7 +1,7 @@ //@ run-pass -//! Test that users can query `Instance::requires_caller_location` to detect -//! `#[track_caller]` functions and the implicit extra argument in their ABI, -//! and that they can generate caller location constants via `Span::caller_location`. +//! Test `#[track_caller]` support in rustc_public: +//! - `Instance::requires_caller_location` detects the implicit extra argument. +//! - `Body::caller_location` resolves the correct location constant. //@ ignore-stage1 //@ ignore-cross-compile @@ -21,85 +21,105 @@ use std::ops::ControlFlow; use rustc_public::crate_def::CrateDef; use rustc_public::mir::mono::Instance; -use rustc_public::mir::TerminatorKind; -use rustc_public::ty::{ConstantKind, RigidTy, TyKind}; +use rustc_public::mir::{Body, TerminatorKind}; +use rustc_public::ty::{ConstantKind, MirConst, RigidTy, TyKind}; const CRATE_NAME: &str = "input"; -fn test_stable_mir() -> ControlFlow<()> { +fn test_track_caller() -> ControlFlow<()> { let items = rustc_public::all_local_items(); - let tracked = items - .iter() - .find(|item| item.name() == "input::tracked_fn") - .expect("missing tracked_fn"); + test_requires_caller_location(&items); + test_caller_location_from_regular_fn(&items); + test_caller_location_propagation(&items); + + ControlFlow::Continue(()) +} + +/// Check `requires_caller_location` and the extra ABI argument. +fn test_requires_caller_location(items: &[rustc_public::CrateItem]) { + let tracked = + items.iter().find(|item| item.name() == "input::tracked_fn").expect("missing tracked_fn"); let not_tracked = items .iter() .find(|item| item.name() == "input::not_tracked_fn") .expect("missing not_tracked_fn"); - let caller = - items.iter().find(|item| item.name() == "input::caller").expect("missing caller"); let tracked_instance = Instance::try_from(*tracked).unwrap(); let not_tracked_instance = Instance::try_from(*not_tracked).unwrap(); - // Check requires_caller_location - assert!( - tracked_instance.requires_caller_location(), - "tracked_fn should require caller location" - ); - assert!( - !not_tracked_instance.requires_caller_location(), - "not_tracked_fn should NOT require caller location" - ); - - // Verify that the ABI reflects the extra argument. - let tracked_abi = tracked_instance.fn_abi().unwrap(); - let not_tracked_abi = not_tracked_instance.fn_abi().unwrap(); + assert!(tracked_instance.requires_caller_location()); + assert!(!not_tracked_instance.requires_caller_location()); // tracked_fn(u32) -> u32: MIR has 1 arg, ABI has 2 (extra &Location) + let tracked_abi = tracked_instance.fn_abi().unwrap(); assert_eq!(tracked_abi.args.len(), 2, "tracked_fn ABI should have 2 args"); + // not_tracked_fn(u32) -> u32: MIR has 1 arg, ABI has 1 + let not_tracked_abi = not_tracked_instance.fn_abi().unwrap(); assert_eq!(not_tracked_abi.args.len(), 1, "not_tracked_fn ABI should have 1 arg"); +} - // Check that calling a #[track_caller] function from the caller's body - // resolves to an instance that requires caller location, and that we can - // generate a caller location constant from the call site span. +/// Check that `Body::caller_location` resolves from the call site span for regular functions. +fn test_caller_location_from_regular_fn(items: &[rustc_public::CrateItem]) { + let caller = items.iter().find(|item| item.name() == "input::caller").expect("missing caller"); + let caller_instance = Instance::try_from(*caller).unwrap(); + assert!(!caller_instance.requires_caller_location()); + + let body = caller_instance.body().unwrap(); + let location = resolve_tracked_call_location(&body, None); + + // The result should be a &'static Location<'static> constant. + assert!( + matches!(location.ty().kind(), TyKind::RigidTy(RigidTy::Ref(..))), + "caller_location should produce a reference type" + ); + assert!( + matches!(location.kind(), ConstantKind::Allocated(..)), + "caller_location should produce an allocated constant" + ); +} + +/// Check that `Body::caller_location` propagates the inherited location for `#[track_caller]` fns. +fn test_caller_location_propagation(items: &[rustc_public::CrateItem]) { + let caller = items.iter().find(|item| item.name() == "input::caller").expect("missing caller"); let caller_instance = Instance::try_from(*caller).unwrap(); let caller_body = caller_instance.body().unwrap(); - for bb in &caller_body.blocks { + + let wrapper = items + .iter() + .find(|item| item.name() == "input::tracked_wrapper") + .expect("missing tracked_wrapper"); + let wrapper_instance = Instance::try_from(*wrapper).unwrap(); + assert!(wrapper_instance.requires_caller_location()); + + let wrapper_body = wrapper_instance.body().unwrap(); + + // Use the location resolved from caller() as the inherited value. + let inherited = resolve_tracked_call_location(&caller_body, None); + + // tracked_wrapper is #[track_caller], so pass the inherited location. + // Without inlining, the inherited value should be returned as-is. + let result = resolve_tracked_call_location(&wrapper_body, Some(inherited.clone())); + assert_eq!(result, inherited, "inherited location should be propagated through"); +} + +/// Find the first call to a `#[track_caller]` function in the body and resolve its location. +fn resolve_tracked_call_location(body: &Body, inherited: Option) -> MirConst { + for bb in &body.blocks { if let TerminatorKind::Call { func, .. } = &bb.terminator.kind { let TyKind::RigidTy(RigidTy::FnDef(def, args)) = - func.ty(caller_body.locals()).unwrap().kind() + func.ty(body.locals()).unwrap().kind() else { continue; }; let callee = Instance::resolve(def, &args).unwrap(); - if callee.mangled_name().contains("tracked_fn") { - assert!( - callee.requires_caller_location(), - "resolved callee should require caller location" - ); - - // Generate a caller location from the call site span. - let call_span = bb.terminator.source_info.span; - let location_const = call_span.as_caller_location(); - // The constant should be a reference type (&'static Location<'static>). - let ty_kind = location_const.ty().kind(); - assert!( - matches!(ty_kind, TyKind::RigidTy(RigidTy::Ref(..))), - "caller_location should produce a reference type, got: {ty_kind:?}" - ); - // The constant should be allocated (a scalar pointer to static data). - assert!( - matches!(location_const.kind(), ConstantKind::Allocated(..)), - "caller_location should produce an allocated constant" - ); + if callee.requires_caller_location() { + return body.caller_location(&bb.terminator, inherited); } } } - - ControlFlow::Continue(()) + panic!("no call to a #[track_caller] function found in body"); } fn main() { @@ -113,7 +133,7 @@ fn main() { CRATE_NAME.to_string(), path.to_string(), ]; - run!(args, test_stable_mir).unwrap(); + run!(args, test_track_caller).unwrap(); } fn generate_input(path: &str) -> std::io::Result<()> { @@ -133,6 +153,11 @@ fn generate_input(path: &str) -> std::io::Result<()> { pub fn caller() -> u32 {{ tracked_fn(42) }} + + #[track_caller] + pub fn tracked_wrapper(x: u32) -> u32 {{ + tracked_fn(x) + }} "# )?; Ok(()) From d3e8e6b450b11d5e268bb9a585c0c38947b1a378 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:13:20 +0200 Subject: [PATCH 15/61] Get rid of expose_provenance on Xous In favor of passing raw pointers to inline asm. --- library/std/src/os/xous/ffi.rs | 50 +++++++++++--------- library/std/src/sys/thread/xous.rs | 6 +-- library/std/src/sys/thread_local/key/xous.rs | 6 +-- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/library/std/src/os/xous/ffi.rs b/library/std/src/os/xous/ffi.rs index e91b11cf17eec..e5938b7bddb53 100644 --- a/library/std/src/os/xous/ffi.rs +++ b/library/std/src/os/xous/ffi.rs @@ -24,7 +24,7 @@ fn lend_mut_impl( let mut a1: usize = connection.try_into().unwrap(); let mut a2 = InvokeType::LendMut as usize; let a3 = opcode; - let a4 = data.as_mut_ptr().expose_provenance(); + let a4 = data.as_mut_ptr(); let a5 = data.len(); let a6 = arg1; let a7 = arg2; @@ -86,7 +86,7 @@ fn lend_impl( let a1: usize = connection.try_into().unwrap(); let a2 = InvokeType::Lend as usize; let a3 = opcode; - let a4 = data.as_ptr().expose_provenance(); + let a4 = data.as_ptr(); let a5 = data.len(); let a6 = arg1; let a7 = arg2; @@ -366,8 +366,10 @@ pub(crate) unsafe fn map_memory( flags: MemoryFlags, ) -> Result<&'static mut [T], Error> { let mut a0 = Syscall::MapMemory as usize; - let mut a1 = phys.map_or(0, |p| p.get()); - let mut a2 = virt.map_or(0, |p| p.as_ptr().expose_provenance()); + let a1 = phys.map_or(0, |p| p.get()); + let a1_out: *mut T; + let a2 = virt.map_or(core::ptr::null(), |p| p.as_ptr()); + let a2_out: usize; let a3 = count * size_of::(); let a4 = flags.bits(); let a5 = 0; @@ -378,8 +380,8 @@ pub(crate) unsafe fn map_memory( core::arch::asm!( "ecall", inlateout("a0") a0, - inlateout("a1") a1, - inlateout("a2") a2, + inlateout("a1") a1 => a1_out, + inlateout("a2") a2 => a2_out, inlateout("a3") a3 => _, inlateout("a4") a4 => _, inlateout("a5") a5 => _, @@ -391,12 +393,12 @@ pub(crate) unsafe fn map_memory( let result = a0; if result == SyscallResult::MemoryRange as usize { - let start = core::ptr::with_exposed_provenance_mut::(a1); - let len = a2 / size_of::(); + let start = a1_out; + let len = a2_out / size_of::(); let end = unsafe { start.add(len) }; Ok(unsafe { core::slice::from_raw_parts_mut(start, len) }) } else if result == SyscallResult::Error as usize { - Err(a1.into()) + Err(a1_out.addr().into()) } else { Err(Error::InternalError) } @@ -408,7 +410,7 @@ pub(crate) unsafe fn map_memory( /// function returns, even if this function returns Err(). pub(crate) unsafe fn unmap_memory(range: *mut [T]) -> Result<(), Error> { let mut a0 = Syscall::UnmapMemory as usize; - let mut a1 = range.as_mut_ptr().expose_provenance(); + let mut a1 = range.as_mut_ptr(); let a2 = range.len() * size_of::(); let a3 = 0; let a4 = 0; @@ -435,7 +437,7 @@ pub(crate) unsafe fn unmap_memory(range: *mut [T]) -> Result<(), Error> { if result == SyscallResult::Ok as usize { Ok(()) } else if result == SyscallResult::Error as usize { - Err(a1.into()) + Err(a1.addr().into()) } else { Err(Error::InternalError) } @@ -454,7 +456,8 @@ pub(crate) unsafe fn update_memory_flags( new_flags: MemoryFlags, ) -> Result<(), Error> { let mut a0 = Syscall::UpdateMemoryFlags as usize; - let mut a1 = range.as_mut_ptr().expose_provenance(); + let a1 = range.as_mut_ptr(); + let a1_out: usize; let a2 = range.len() * size_of::(); let a3 = new_flags.bits(); let a4 = 0; // Process ID is currently None @@ -466,7 +469,7 @@ pub(crate) unsafe fn update_memory_flags( core::arch::asm!( "ecall", inlateout("a0") a0, - inlateout("a1") a1, + inlateout("a1") a1 => a1_out, inlateout("a2") a2 => _, inlateout("a3") a3 => _, inlateout("a4") a4 => _, @@ -481,24 +484,25 @@ pub(crate) unsafe fn update_memory_flags( if result == SyscallResult::Ok as usize { Ok(()) } else if result == SyscallResult::Error as usize { - Err(a1.into()) + Err(a1_out.into()) } else { Err(Error::InternalError) } } /// Creates a thread with a given stack and up to four arguments. -pub(crate) fn create_thread( - start: *mut usize, +pub(crate) fn create_thread( + start: extern "C" fn(*mut usize, usize, usize) -> !, stack: *mut [u8], - arg0: usize, - arg1: usize, + arg0: *mut T, + arg1: *const u8, arg2: usize, arg3: usize, ) -> Result { let mut a0 = Syscall::CreateThread as usize; - let mut a1 = start.expose_provenance(); - let a2 = stack.as_mut_ptr().expose_provenance(); + let a1 = start; + let a1_out: usize; + let a2 = stack.as_mut_ptr(); let a3 = stack.len(); let a4 = arg0; let a5 = arg1; @@ -509,7 +513,7 @@ pub(crate) fn create_thread( core::arch::asm!( "ecall", inlateout("a0") a0, - inlateout("a1") a1, + inlateout("a1") a1 => a1_out, inlateout("a2") a2 => _, inlateout("a3") a3 => _, inlateout("a4") a4 => _, @@ -522,9 +526,9 @@ pub(crate) fn create_thread( let result = a0; if result == SyscallResult::ThreadId as usize { - Ok(a1.into()) + Ok(a1_out.into()) } else if result == SyscallResult::Error as usize { - Err(a1.into()) + Err(a1_out.into()) } else { Err(Error::InternalError) } diff --git a/library/std/src/sys/thread/xous.rs b/library/std/src/sys/thread/xous.rs index 1a32d87ed7162..002d1012d5604 100644 --- a/library/std/src/sys/thread/xous.rs +++ b/library/std/src/sys/thread/xous.rs @@ -58,11 +58,11 @@ impl Thread { .map_err(|code| io::Error::from_raw_os_error(code as i32))? }; - let guard_page_pre = stack_plus_guard_pages.as_ptr().expose_provenance(); + let guard_page_pre = stack_plus_guard_pages.as_ptr(); let tid = create_thread( - thread_start as *mut usize, + thread_start, &mut stack_plus_guard_pages[GUARD_PAGE_SIZE..(stack_size + GUARD_PAGE_SIZE)], - data.expose_provenance(), + data, guard_page_pre, stack_size, 0, diff --git a/library/std/src/sys/thread_local/key/xous.rs b/library/std/src/sys/thread_local/key/xous.rs index a37808a2d7824..394d1b08ece3a 100644 --- a/library/std/src/sys/thread_local/key/xous.rs +++ b/library/std/src/sys/thread_local/key/xous.rs @@ -70,14 +70,14 @@ unsafe extern "Rust" { #[inline] fn tls_ptr_addr() -> *mut *mut u8 { - let mut tp: usize; + let tp: *mut *mut u8; unsafe { asm!( "mv {}, tp", out(reg) tp, ); } - core::ptr::with_exposed_provenance_mut::<*mut u8>(tp) + tp } /// Creates an area of memory that's unique per thread. This area will @@ -115,7 +115,7 @@ fn tls_table_slow() -> &'static mut [*mut u8] { // Set the thread's `$tp` register asm!( "mv tp, {}", - in(reg) tp.as_mut_ptr().expose_provenance(), + in(reg) tp.as_mut_ptr(), ); } tp From 7b0af9269df25e0268493f3da9bb585c42115350 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:35:51 +0200 Subject: [PATCH 16/61] Review comments --- library/std/src/os/xous/ffi.rs | 6 ++++-- library/std/src/sys/thread/xous.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/library/std/src/os/xous/ffi.rs b/library/std/src/os/xous/ffi.rs index e5938b7bddb53..8a499446e98d0 100644 --- a/library/std/src/os/xous/ffi.rs +++ b/library/std/src/os/xous/ffi.rs @@ -359,6 +359,8 @@ pub(crate) fn do_yield() { /// This function is safe unless a virtual address is specified. In that case, /// the kernel will return an alias to the existing range. This violates Rust's /// pointer uniqueness guarantee. +// The phys argument uses an integer rather than pointer type as pointer +// provenance only covers virtual memory, not physical memory. pub(crate) unsafe fn map_memory( phys: Option, virt: Option>, @@ -491,8 +493,8 @@ pub(crate) unsafe fn update_memory_flags( } /// Creates a thread with a given stack and up to four arguments. -pub(crate) fn create_thread( - start: extern "C" fn(*mut usize, usize, usize) -> !, +pub(crate) unsafe fn create_thread( + start: unsafe extern "C" fn(*mut usize, usize, usize) -> !, stack: *mut [u8], arg0: *mut T, arg1: *const u8, diff --git a/library/std/src/sys/thread/xous.rs b/library/std/src/sys/thread/xous.rs index 002d1012d5604..3ddd45851dde2 100644 --- a/library/std/src/sys/thread/xous.rs +++ b/library/std/src/sys/thread/xous.rs @@ -75,7 +75,7 @@ impl Thread { rust_start(); } - extern "C" fn thread_start( + unsafe extern "C" fn thread_start( data: *mut usize, guard_page_pre: usize, stack_size: usize, From 10100f263cfc039ae167363293482f82adc855d1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 21 Jul 2026 10:26:36 -0700 Subject: [PATCH 17/61] Update wasm-component-ld to 0.5.27 Keeping up-to-date --- Cargo.lock | 74 +++++++++++++------------- src/tools/wasm-component-ld/Cargo.toml | 2 +- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d5a3a9974fe5e..fd9f5d3782066 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -871,7 +871,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1284,7 +1284,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1411,7 +1411,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2198,7 +2198,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2685,7 +2685,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5113,7 +5113,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5545,7 +5545,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5564,7 +5564,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6367,9 +6367,9 @@ dependencies = [ [[package]] name = "wasm-component-ld" -version = "0.5.26" +version = "0.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51a12709376d4ce64f472699500db3b0e5902cc2bef16fb6ca3098bfdac032fa" +checksum = "5fd1ba338b2c06654988a76b13f38cad9a1c738e3275bf71ecf70eeac5d98eb4" dependencies = [ "anyhow", "clap", @@ -6378,12 +6378,12 @@ dependencies = [ "libc", "tempfile", "wasi-preview1-component-adapter-provider", - "wasmparser 0.253.0", + "wasmparser 0.254.0", "wat", "windows-sys 0.61.2", "winsplit", - "wit-component 0.253.0", - "wit-parser 0.253.0", + "wit-component 0.254.0", + "wit-parser 0.254.0", ] [[package]] @@ -6415,12 +6415,12 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.253.0" +version = "0.254.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59972d6cd272259de647b7c1f1912e45e289c75ffd4be04e10695507cd7e1b59" +checksum = "09480d646178e5fdd12bb06e812d0af9a3a191dbc9cd697fdc86687beade7393" dependencies = [ "leb128fmt", - "wasmparser 0.253.0", + "wasmparser 0.254.0", ] [[package]] @@ -6437,14 +6437,14 @@ dependencies = [ [[package]] name = "wasm-metadata" -version = "0.253.0" +version = "0.254.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3f45816ef616806f48498bcd831377de578c4fa51db0c83ab8ceb78cc13523b" +checksum = "b01df5f3b4ca7881e843f3bc0fb8a3905d79c68692250dcb8e33e698705ccdb6" dependencies = [ "anyhow", "indexmap", - "wasm-encoder 0.253.0", - "wasmparser 0.253.0", + "wasm-encoder 0.254.0", + "wasmparser 0.254.0", ] [[package]] @@ -6481,9 +6481,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.253.0" +version = "0.254.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19db11f87d2486580e1e8b6f494c54df7e0566b87d0b599db843c24019667339" +checksum = "d5769a29f799fbab136aaf65b4fe5384cd7d93fe6fc9ba0dcb6c8382a1f16e27" dependencies = [ "bitflags", "hashbrown 0.17.0", @@ -6494,22 +6494,22 @@ dependencies = [ [[package]] name = "wast" -version = "253.0.0" +version = "254.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3264542f8965c5d84fb1085d924bfba9a6314bb228eff13a2de14d7627664d0" +checksum = "e7ed4dfc8f6b9fc38b231065e2cdfbf7359af5ab945990abf09658dcc63c3e32" dependencies = [ "bumpalo", "leb128fmt", "memchr", "unicode-width 0.2.2", - "wasm-encoder 0.253.0", + "wasm-encoder 0.254.0", ] [[package]] name = "wat" -version = "1.253.0" +version = "1.254.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bfc5ce906144200c972ec617470aa35bd847472e170b26dde3e80541c674055" +checksum = "7127f7f9b8f127c879991cecd35f494e4628bae1b0874c681414d8d8831e952c" dependencies = [ "wast", ] @@ -6542,7 +6542,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6957,9 +6957,9 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.253.0" +version = "0.254.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbbd2500ac3488489ee8c6e59b79d7e47e6da5bfb019efd35d5dca57b78af624" +checksum = "b0e65bb94c369b3c4741ce3d1d2704b1fec93db7c540df0e521a097e7ceeb5be" dependencies = [ "anyhow", "bitflags", @@ -6968,10 +6968,10 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.253.0", - "wasm-metadata 0.253.0", - "wasmparser 0.253.0", - "wit-parser 0.253.0", + "wasm-encoder 0.254.0", + "wasm-metadata 0.254.0", + "wasmparser 0.254.0", + "wit-parser 0.254.0", ] [[package]] @@ -6994,9 +6994,9 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.253.0" +version = "0.254.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d997b8e5920fcbeec742b58e583325d6419a6aca617ae8075c406a61c65ba8a" +checksum = "1655131e4f7d3f0cb141f6eca71315ca40eff0f3d4de7cff0a82bacedd8c89b4" dependencies = [ "anyhow", "hashbrown 0.17.0", @@ -7008,7 +7008,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-ident", - "wasmparser 0.253.0", + "wasmparser 0.254.0", ] [[package]] diff --git a/src/tools/wasm-component-ld/Cargo.toml b/src/tools/wasm-component-ld/Cargo.toml index a07c2029f4b38..5a7e253b4fdb4 100644 --- a/src/tools/wasm-component-ld/Cargo.toml +++ b/src/tools/wasm-component-ld/Cargo.toml @@ -10,4 +10,4 @@ name = "wasm-component-ld" path = "src/main.rs" [dependencies] -wasm-component-ld = "0.5.26" +wasm-component-ld = "0.5.27" From ebbdcf083e18efe6097597707c46a5b4644fe2db Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Tue, 21 Jul 2026 20:21:19 -0700 Subject: [PATCH 18/61] Split non-local `semicolon_in_expressions_from_macros` into a separate lint The new handling for non-local macros in `semicolon_in_expressions_from_macros` produces new deny-by-default lints on various existing crates, which have not previously received warnings because prior Rust versions suppressed them. Split those out into a new `semicolon_in_expressions_from_non_local_macros` lint, and make that one warn-by-default (and FCW warn-in-deps), rather than deny-by-default, to give the ecosystem time to adapt. This reintroduces the `is_local` tracking from commit 9192337331dc8c59c6367d316c32f08f975b7140. --- compiler/rustc_expand/src/base.rs | 5 +++- compiler/rustc_expand/src/mbe/macro_rules.rs | 18 +++++++++-- compiler/rustc_lint_defs/src/builtin.rs | 30 +++++++++++++++++++ .../foreign-crate.rs | 1 + .../foreign-crate.stderr | 16 +++++++--- 5 files changed, 62 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index 93b02989eb8b0..e618f7431d3b6 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -277,7 +277,10 @@ impl<'cx> MacroExpanderResult<'cx> { arm_span: Span, macro_ident: Ident, ) -> Self { - let parser = ParserAnyMacro::from_tts(cx, tts, site_span, arm_span, macro_ident, &[], &[]); + // Emit SEMICOLON_IN_EXPRESSIONS_FROM_MACROS here, rather than the NON_LOCAL version. + let is_local = true; + let parser = + ParserAnyMacro::from_tts(cx, tts, site_span, arm_span, is_local, macro_ident, &[], &[]); ExpandResult::Ready(Box::new(parser)) } } diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index 0fced30420ebd..f6d12fa19c141 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -19,6 +19,7 @@ use rustc_hir::def::MacroKinds; use rustc_hir::find_attr; use rustc_lint_defs::builtin::{ RUST_2021_INCOMPATIBLE_OR_PATTERNS, SEMICOLON_IN_EXPRESSIONS_FROM_MACROS, + SEMICOLON_IN_EXPRESSIONS_FROM_NON_LOCAL_MACROS, }; use rustc_parse::exp; use rustc_parse::parser::{Parser, Recovery}; @@ -55,6 +56,8 @@ pub(crate) struct ParserAnyMacro<'a, 'b> { lint_node_id: NodeId, is_trailing_mac: bool, arm_span: Span, + /// Whether or not this macro is defined in the current crate + is_local: bool, bindings: &'b [MacroRule], matched_rule_bindings: &'b [MatcherLoc], } @@ -71,6 +74,7 @@ impl<'a, 'b> ParserAnyMacro<'a, 'b> { lint_node_id, arm_span, is_trailing_mac, + is_local, bindings, matched_rule_bindings, } = *self; @@ -96,8 +100,13 @@ impl<'a, 'b> ParserAnyMacro<'a, 'b> { // `macro_rules! m { () => { panic!(); } }` isn't parsed by `.parse_expr()`, // but `m!()` is allowed in expression positions (cf. issue #34706). if kind == AstFragmentKind::Expr && parser.token == token::Semi { + let lint = if is_local { + SEMICOLON_IN_EXPRESSIONS_FROM_MACROS + } else { + SEMICOLON_IN_EXPRESSIONS_FROM_NON_LOCAL_MACROS + }; parser.psess.buffer_lint( - SEMICOLON_IN_EXPRESSIONS_FROM_MACROS, + lint, parser.token.span, lint_node_id, diagnostics::TrailingMacro { is_trailing: is_trailing_mac, name: macro_ident }, @@ -117,6 +126,7 @@ impl<'a, 'b> ParserAnyMacro<'a, 'b> { tts: TokenStream, site_span: Span, arm_span: Span, + is_local: bool, macro_ident: Ident, // bindings and lhs is for diagnostics bindings: &'b [MacroRule], @@ -133,6 +143,7 @@ impl<'a, 'b> ParserAnyMacro<'a, 'b> { lint_node_id: cx.current_expansion.lint_node_id, is_trailing_mac: cx.current_expansion.is_trailing_mac, arm_span, + is_local, bindings, matched_rule_bindings, } @@ -464,12 +475,13 @@ fn expand_macro<'cx, 'a: 'cx>( trace_macros_note(&mut cx.expansions, sp, msg); } - if is_defined_in_current_crate(node_id) { + let is_local = is_defined_in_current_crate(node_id); + if is_local { cx.resolver.record_macro_rule_usage(node_id, rule_index); } // Let the context choose how to interpret the result. Weird, but useful for X-macros. - Box::new(ParserAnyMacro::from_tts(cx, tts, sp, arm_span, name, rules, lhs)) + Box::new(ParserAnyMacro::from_tts(cx, tts, sp, arm_span, is_local, name, rules, lhs)) } Err(CanRetry::No(guar)) => { debug!("Will not retry matching as an error was emitted already"); diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 44af1ac961e2f..7c0e4eb3fe199 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -104,6 +104,7 @@ pub mod hardwired { RUST_2024_PRELUDE_COLLISIONS, SELF_CONSTRUCTOR_FROM_OUTER_ITEM, SEMICOLON_IN_EXPRESSIONS_FROM_MACROS, + SEMICOLON_IN_EXPRESSIONS_FROM_NON_LOCAL_MACROS, SHADOWING_SUPERTRAIT_ITEMS, SINGLE_USE_LIFETIMES, STABLE_FEATURES, @@ -2890,6 +2891,35 @@ declare_lint! { }; } +declare_lint! { + /// The `semicolon_in_expressions_from_non_local_macros` lint detects trailing semicolons in + /// macro bodies when the macro is invoked in expression position. This was previously accepted, + /// but is being phased out. This is similar to the `semicolon_in_expressions_from_macros` lint, + /// but applies to macros expanded from a different crate. + /// + /// ### Explanation + /// + /// Previous, Rust ignored trailing semicolon in a macro + /// body when a macro was invoked in expression position. + /// However, this makes the treatment of semicolons in the language + /// inconsistent, and could lead to unexpected runtime behavior + /// in some circumstances (e.g. if the macro author expects + /// a value to be dropped). + /// + /// This is a [future-incompatible] lint to transition this + /// to a hard error in the future. See [issue #79813] for more details. + /// + /// [issue #79813]: https://github.com/rust-lang/rust/issues/79813 + /// [future-incompatible]: ../index.md#future-incompatible-lints + pub SEMICOLON_IN_EXPRESSIONS_FROM_NON_LOCAL_MACROS, + Warn, + "trailing semicolon in macro body used as expression", + @future_incompatible = FutureIncompatibleInfo { + reason: fcw!(FutureReleaseError #79813), + report_in_deps: true, + }; +} + declare_lint! { /// The `legacy_derive_helpers` lint detects derive helper attributes /// that are used before they are introduced. diff --git a/tests/ui/lint/semicolon-in-expressions-from-macros/foreign-crate.rs b/tests/ui/lint/semicolon-in-expressions-from-macros/foreign-crate.rs index a0fc12470def0..b9451f9042061 100644 --- a/tests/ui/lint/semicolon-in-expressions-from-macros/foreign-crate.rs +++ b/tests/ui/lint/semicolon-in-expressions-from-macros/foreign-crate.rs @@ -1,4 +1,5 @@ //@ aux-build:foreign-crate.rs +#![deny(semicolon_in_expressions_from_non_local_macros)] extern crate foreign_crate; diff --git a/tests/ui/lint/semicolon-in-expressions-from-macros/foreign-crate.stderr b/tests/ui/lint/semicolon-in-expressions-from-macros/foreign-crate.stderr index c68de5e4b6d5c..b335df57496ef 100644 --- a/tests/ui/lint/semicolon-in-expressions-from-macros/foreign-crate.stderr +++ b/tests/ui/lint/semicolon-in-expressions-from-macros/foreign-crate.stderr @@ -1,25 +1,33 @@ error: trailing semicolon in macro used in expression position - --> $DIR/foreign-crate.rs:7:13 + --> $DIR/foreign-crate.rs:8:13 | LL | let _ = foreign_crate::my_macro!(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #79813 - = note: `#[deny(semicolon_in_expressions_from_macros)]` (part of `#[deny(future_incompatible)]`) on by default +note: the lint level is defined here + --> $DIR/foreign-crate.rs:2:9 + | +LL | #![deny(semicolon_in_expressions_from_non_local_macros)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the macro `foreign_crate::my_macro` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 1 previous error Future incompatibility report: Future breakage diagnostic: error: trailing semicolon in macro used in expression position - --> $DIR/foreign-crate.rs:7:13 + --> $DIR/foreign-crate.rs:8:13 | LL | let _ = foreign_crate::my_macro!(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #79813 - = note: `#[deny(semicolon_in_expressions_from_macros)]` (part of `#[deny(future_incompatible)]`) on by default +note: the lint level is defined here + --> $DIR/foreign-crate.rs:2:9 + | +LL | #![deny(semicolon_in_expressions_from_non_local_macros)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the macro `foreign_crate::my_macro` (in Nightly builds, run with -Z macro-backtrace for more info) From c1a4b72b398d36403e720cab432f1e4cb15d7421 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Tue, 21 Jul 2026 21:21:32 -0700 Subject: [PATCH 19/61] Add (manual) example for `semicolon_in_expressions_from_non_local_macros` The automatic mechanism to produce lint examples won't work, since this lint needs a separate crate, so add a `rust,ignore` example and manually provide the compiler output. --- compiler/rustc_lint_defs/src/builtin.rs | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 7c0e4eb3fe199..82b4dd5e2babd 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -2897,6 +2897,41 @@ declare_lint! { /// but is being phased out. This is similar to the `semicolon_in_expressions_from_macros` lint, /// but applies to macros expanded from a different crate. /// + /// ### Example + /// + /// ```rust,ignore (needs separate file) + /// fn main() { + /// let val = match true { + /// true => false, + /// _ => example_separate_crate::foo!() + /// }; + /// } + /// ``` + /// + /// where the crate `example-separate-crate` contains: + /// + /// ```rust,ignore (must be compiled as separate crate) + /// #[macro_export] + /// macro_rules! foo { + /// () => { true; } + /// } + /// ``` + /// + /// produces: + /// + /// ```text + /// warning: trailing semicolon in macro used in expression position + /// --> src/main.rs:4:14 + /// | + /// 4 | _ => example_separate_crate::foo!() + /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + /// | + /// = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + /// = note: for more information, see issue #79813 + /// = note: `#[warn(semicolon_in_expressions_from_non_local_macros)]` (part of `#[warn(future_incompatible)]`) on by default + /// = note: this warning originates in the macro `example_separate_crate::foo` (in Nightly builds, run with -Z macro-backtrace for more info) + /// ``` + /// /// ### Explanation /// /// Previous, Rust ignored trailing semicolon in a macro From 66d0fb88ebb382d2ff8d292062afe26263e1d3a7 Mon Sep 17 00:00:00 2001 From: lcnr Date: Thu, 16 Apr 2026 12:34:20 +0200 Subject: [PATCH 20/61] stepping into `NormalizesTo` where-clauses may be productive --- .../src/solve/eval_ctxt/mod.rs | 12 +--- .../next-solver-region-resolution.rs | 3 +- .../next-solver-region-resolution.stderr | 22 +++++-- .../normalizes-to-is-not-productive-2.rs | 8 ++- .../cycles/normalizes-to-is-not-productive.rs | 14 ++-- .../normalizes-to-is-not-productive.stderr | 66 ++++++------------- 6 files changed, 54 insertions(+), 71 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index c3ccb46069063..16c119c6c2cdf 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -338,18 +338,12 @@ where // We currently only consider a cycle coinductive if it steps // into a where-clause of a coinductive trait. CurrentGoalKind::CoinductiveTrait => PathKind::Coinductive, - // While normalizing via an impl does step into a where-clause of - // an impl, accessing the associated item immediately steps out of - // it again. This means cycles/recursive calls are not guarded - // by impls used for normalization. - // - // See tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.rs - // for how this can go wrong. - CurrentGoalKind::ProjectionComputeAssocTermCandidate => PathKind::Inductive, // We probably want to make all traits coinductive in the future, // so we treat cycles involving where-clauses of not-yet coinductive // traits as ambiguous for now. - CurrentGoalKind::Misc => PathKind::Unknown, + CurrentGoalKind::Misc | CurrentGoalKind::ProjectionComputeAssocTermCandidate => { + PathKind::Unknown + } }, // Relating types is always unproductive. If we were to map proof trees to // corecursive functions as explained in #136824, relating types never diff --git a/tests/ui/specialization/min_specialization/next-solver-region-resolution.rs b/tests/ui/specialization/min_specialization/next-solver-region-resolution.rs index 16c0f80b1c8dd..a49fe6184890f 100644 --- a/tests/ui/specialization/min_specialization/next-solver-region-resolution.rs +++ b/tests/ui/specialization/min_specialization/next-solver-region-resolution.rs @@ -3,7 +3,7 @@ #![feature(min_specialization)] -trait Foo { +trait Foo { //~ ERROR cycle detected when coherence checking all impls of trait `Foo` type Item; } @@ -16,7 +16,6 @@ where } impl<'a, T> Foo for &T -//~^ ERROR: cycle detected when computing normalized predicates of `` where Self::Item: Baz, { diff --git a/tests/ui/specialization/min_specialization/next-solver-region-resolution.stderr b/tests/ui/specialization/min_specialization/next-solver-region-resolution.stderr index 4c2dc5e8e71fa..76d17e33c30ac 100644 --- a/tests/ui/specialization/min_specialization/next-solver-region-resolution.stderr +++ b/tests/ui/specialization/min_specialization/next-solver-region-resolution.stderr @@ -1,14 +1,26 @@ -error[E0391]: cycle detected when computing normalized predicates of `` +error[E0391]: cycle detected when coherence checking all impls of trait `Foo` + --> $DIR/next-solver-region-resolution.rs:6:1 + | +LL | trait Foo { + | ^^^^^^^^^ + | + = note: ...which requires building specialization graph of trait `Foo`... +note: ...which requires computing whether impls specialize one another... + --> $DIR/next-solver-region-resolution.rs:12:1 + | +LL | / impl<'a, T> Foo for &'a T +LL | | where +LL | | Self::Item: 'a, + | |___________________^ +note: ...which requires computing normalized predicates of ``... --> $DIR/next-solver-region-resolution.rs:18:1 | LL | / impl<'a, T> Foo for &T -LL | | LL | | where LL | | Self::Item: Baz, | |____________________^ - | - = note: ...which immediately requires computing normalized predicates of `` again -note: cycle used when computing whether impls specialize one another + = note: ...which again requires coherence checking all impls of trait `Foo`, completing the cycle +note: cycle used when checking that `` is well-formed --> $DIR/next-solver-region-resolution.rs:12:1 | LL | / impl<'a, T> Foo for &'a T diff --git a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive-2.rs b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive-2.rs index bb3540f9a214f..22a30cc4cd1d8 100644 --- a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive-2.rs +++ b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive-2.rs @@ -3,7 +3,7 @@ //@[next] compile-flags: -Znext-solver //@ check-pass -// Regression test for trait-system-refactor-initiative#176. +// Test from trait-system-refactor-initiative#176. // // Normalizing ` as IntoIterator>::IntoIter` has two candidates // inside of the function: @@ -13,7 +13,11 @@ // - where-clause requires ` as IntoIterator>::IntoIter eq Vec` // - normalize ` as IntoIterator>::IntoIter` again, cycle // -// We need to treat this cycle as an error to be able to use the actual impl. +// The blanket impl is unfortunately also a productive cycle, so we have to +// break this code, see trait-system-refactor-initiative#273. +// +// As we currently incorrectly treat aliases in the environment as rigid, this compiles +// even with the new solver, see #158643. fn test() where diff --git a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.rs b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.rs index 83e300b077428..cb37d2c457a42 100644 --- a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.rs +++ b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.rs @@ -1,9 +1,10 @@ //@ ignore-compare-mode-next-solver (explicit) //@ compile-flags: -Znext-solver -// Make sure that stepping into impl where-clauses of `NormalizesTo` -// goals is unproductive. This must not compile, see the inline -// comments. +// A test for the cycle handling when stepping into where-clauses of `NormalizesTo`. +// Whether stepping into where-clauses is productive depends on how they are used. +// +// In this concrete test, the cycle must not be productive. trait Bound { fn method(); @@ -30,7 +31,7 @@ fn impls_bound() { // The where-clause requires `Foo: Trait` to hold to be wf. // If stepping into where-clauses during normalization is considered -// to be productive, this would be the case: +// to be productive here, this would be the case: // // - `Foo: Trait` // - via blanket impls, requires `Foo: Bound` @@ -41,12 +42,13 @@ fn generic() //~^ ERROR the trait bound `Foo: Bound` is not satisfied where >::Assoc: Bound, - //~^ ERROR the trait bound `Foo: Bound` is not satisfied + //~^ ERROR overflow evaluating the requirement `>::Assoc: Bound` + //~| ERROR overflow evaluating whether `>::Assoc` is well-formed { // Requires proving `Foo: Bound` by normalizing // `>::Assoc` to `Foo`. impls_bound::(); - //~^ ERROR the trait bound `Foo: Bound` is not satisfied + //~^ ERROR overflow evaluating the requirement `Foo: Bound` } fn main() { // Requires proving `>::Assoc: Bound`. diff --git a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.stderr b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.stderr index e18265190278a..7aeff2d06dabb 100644 --- a/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.stderr +++ b/tests/ui/traits/next-solver/cycles/normalizes-to-is-not-productive.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `Foo: Bound` is not satisfied - --> $DIR/normalizes-to-is-not-productive.rs:40:1 + --> $DIR/normalizes-to-is-not-productive.rs:41:1 | LL | / fn generic() LL | | @@ -8,76 +8,48 @@ LL | | >::Assoc: Bound, | |____________________________________^ unsatisfied trait bound | help: the trait `Bound` is not implemented for `Foo` - --> $DIR/normalizes-to-is-not-productive.rs:18:1 + --> $DIR/normalizes-to-is-not-productive.rs:19:1 | LL | struct Foo; | ^^^^^^^^^^ help: the trait `Bound` is implemented for `u32` - --> $DIR/normalizes-to-is-not-productive.rs:11:1 + --> $DIR/normalizes-to-is-not-productive.rs:12:1 | LL | impl Bound for u32 { | ^^^^^^^^^^^^^^^^^^ note: required for `Foo` to implement `Trait` - --> $DIR/normalizes-to-is-not-productive.rs:23:19 + --> $DIR/normalizes-to-is-not-productive.rs:24:19 | LL | impl Trait for T { | ----- ^^^^^^^^ ^ | | | unsatisfied trait bound introduced here -error[E0277]: the trait bound `Foo: Bound` is not satisfied - --> $DIR/normalizes-to-is-not-productive.rs:43:31 +error[E0275]: overflow evaluating the requirement `>::Assoc: Bound` + --> $DIR/normalizes-to-is-not-productive.rs:44:31 | LL | >::Assoc: Bound, - | ^^^^^ unsatisfied trait bound - | -help: the trait `Bound` is not implemented for `Foo` - --> $DIR/normalizes-to-is-not-productive.rs:18:1 - | -LL | struct Foo; - | ^^^^^^^^^^ -help: the trait `Bound` is implemented for `u32` - --> $DIR/normalizes-to-is-not-productive.rs:11:1 - | -LL | impl Bound for u32 { - | ^^^^^^^^^^^^^^^^^^ -note: required for `Foo` to implement `Trait` - --> $DIR/normalizes-to-is-not-productive.rs:23:19 - | -LL | impl Trait for T { - | ----- ^^^^^^^^ ^ - | | - | unsatisfied trait bound introduced here -note: required by a bound in `Bound` - --> $DIR/normalizes-to-is-not-productive.rs:8:1 + | ^^^^^ + +error[E0275]: overflow evaluating whether `>::Assoc` is well-formed + --> $DIR/normalizes-to-is-not-productive.rs:44:31 | -LL | / trait Bound { -LL | | fn method(); -LL | | } - | |_^ required by this bound in `Bound` +LL | >::Assoc: Bound, + | ^^^^^ -error[E0277]: the trait bound `Foo: Bound` is not satisfied - --> $DIR/normalizes-to-is-not-productive.rs:48:19 +error[E0275]: overflow evaluating the requirement `Foo: Bound` + --> $DIR/normalizes-to-is-not-productive.rs:50:19 | LL | impls_bound::(); - | ^^^ unsatisfied trait bound - | -help: the trait `Bound` is not implemented for `Foo` - --> $DIR/normalizes-to-is-not-productive.rs:18:1 + | ^^^ | -LL | struct Foo; - | ^^^^^^^^^^ -help: the trait `Bound` is implemented for `u32` - --> $DIR/normalizes-to-is-not-productive.rs:11:1 - | -LL | impl Bound for u32 { - | ^^^^^^^^^^^^^^^^^^ note: required by a bound in `impls_bound` - --> $DIR/normalizes-to-is-not-productive.rs:27:19 + --> $DIR/normalizes-to-is-not-productive.rs:28:19 | LL | fn impls_bound() { | ^^^^^ required by this bound in `impls_bound` -error: aborting due to 3 previous errors +error: aborting due to 4 previous errors -For more information about this error, try `rustc --explain E0277`. +Some errors have detailed explanations: E0275, E0277. +For more information about an error, try `rustc --explain E0275`. From 4233b3bd13283120c3065b4d10bd09a0404eddf4 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 12 Mar 2025 22:48:32 +0000 Subject: [PATCH 21/61] Collect constants within `global_asm!` in collector This is currently a no-op, but will be useful when const in `global_asm!` can be pointers. --- compiler/rustc_monomorphize/src/collector.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 631d28d6cef00..b7beabf548dc4 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -503,10 +503,18 @@ fn collect_items_rec<'tcx>( if let hir::ItemKind::GlobalAsm { asm, .. } = item.kind { for (op, op_sp) in asm.operands { match *op { - hir::InlineAsmOperand::Const { .. } => { - // Only constants which resolve to a plain integer - // are supported. Therefore the value should not - // depend on any other items. + hir::InlineAsmOperand::Const { anon_const } => { + match tcx.const_eval_poly(anon_const.def_id.to_def_id()) { + Ok(val) => { + collect_const_value(tcx, val, &mut used_items); + } + Err(ErrorHandled::TooGeneric(..)) => { + span_bug!(*op_sp, "asm const cannot be resolved; too generic") + } + Err(ErrorHandled::Reported(..)) => { + continue; + } + } } hir::InlineAsmOperand::SymFn { expr } => { let fn_ty = tcx.typeck(item_id.owner_id).expr_ty(expr); From f44536c43a8d6138d4940439723b0eab8e0cd355 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 5 Dec 2025 19:31:28 +0000 Subject: [PATCH 22/61] Add FnDef/Closure -> FnPtr coercion for inline asm const operand --- compiler/rustc_hir_typeck/src/expr.rs | 35 ++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 3bcad2460e78f..6ffbbed8fd642 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -3731,7 +3731,40 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } hir::InlineAsmOperand::Const { ref anon_const } => { - self.check_expr_const_block(anon_const, Expectation::NoExpectation); + // This is mostly similar to type-checking of inline const expressions `const { ... }`, however + // asm const has special coercion rules (per RFC 3848) where function items and closures are coerced to + // function pointers (while pointers and integer remain as-is). + let body = self.tcx.hir_body(anon_const.body); + + let fcx = FnCtxt::new(self, self.param_env, anon_const.def_id); + let ty = fcx.check_expr(body.value); + let target_ty = match self.structurally_resolve_type(body.value.span, ty).kind() + { + ty::FnDef(..) => { + let fn_sig = ty.fn_sig(self.tcx()); + Ty::new_fn_ptr(self.tcx(), fn_sig) + } + ty::Closure(_, args) => { + let closure_sig = args.as_closure().sig(); + let fn_sig = + self.tcx().signature_unclosure(closure_sig, hir::Safety::Safe); + Ty::new_fn_ptr(self.tcx(), fn_sig) + } + _ => ty, + }; + + if let Err(diag) = + self.demand_coerce_diag(&body.value, ty, target_ty, None, AllowTwoPhase::No) + { + diag.emit(); + } + + fcx.require_type_is_sized( + target_ty, + body.value.span, + ObligationCauseCode::SizedConstOrStatic, + ); + fcx.write_ty(anon_const.hir_id, target_ty); } hir::InlineAsmOperand::SymFn { expr } => { self.check_expr(expr); From bf74b6c9abf6a4491e6843f55f17898a41814c0d Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 15 Dec 2025 19:36:35 +0000 Subject: [PATCH 23/61] Give global_asm symbol names Currently global_asm already have symbol names when using v0 scheme, this makes them obtain symbols with legacy scheme too. --- compiler/rustc_middle/src/mono.rs | 2 +- compiler/rustc_symbol_mangling/src/legacy.rs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_middle/src/mono.rs b/compiler/rustc_middle/src/mono.rs index bad6986a2630c..657ea939844cb 100644 --- a/compiler/rustc_middle/src/mono.rs +++ b/compiler/rustc_middle/src/mono.rs @@ -124,7 +124,7 @@ impl<'tcx> MonoItem<'tcx> { MonoItem::Fn(instance) => tcx.symbol_name(instance), MonoItem::Static(def_id) => tcx.symbol_name(Instance::mono(tcx, def_id)), MonoItem::GlobalAsm(item_id) => { - SymbolName::new(tcx, &format!("global_asm_{:?}", item_id.owner_id)) + tcx.symbol_name(Instance::mono(tcx, item_id.owner_id.to_def_id())) } } } diff --git a/compiler/rustc_symbol_mangling/src/legacy.rs b/compiler/rustc_symbol_mangling/src/legacy.rs index 4cce56cf90426..a275c68bbdc17 100644 --- a/compiler/rustc_symbol_mangling/src/legacy.rs +++ b/compiler/rustc_symbol_mangling/src/legacy.rs @@ -37,6 +37,11 @@ pub(super) fn mangle<'tcx>( debug!(?instance_ty); break; } + DefPathData::GlobalAsm => { + // `global_asm!` doesn't have a type. + instance_ty = tcx.types.unit; + break; + } _ => { // if we're making a symbol for something, there ought // to be a value or type-def or something in there From b44bea2b5d994368219ef8e284d76eaff3ea2383 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 18 Dec 2025 17:41:19 +0000 Subject: [PATCH 24/61] Delay stringification of const to backend --- .../rustc_codegen_cranelift/src/global_asm.rs | 10 ++++-- .../rustc_codegen_cranelift/src/inline_asm.rs | 10 +++++- compiler/rustc_codegen_gcc/src/asm.rs | 31 ++++++++++++++----- compiler/rustc_codegen_llvm/src/asm.rs | 22 ++++++++++--- compiler/rustc_codegen_ssa/src/base.rs | 29 +++++++++-------- compiler/rustc_codegen_ssa/src/common.rs | 8 ++--- compiler/rustc_codegen_ssa/src/mir/block.rs | 15 ++++----- .../rustc_codegen_ssa/src/mir/naked_asm.rs | 21 ++++++------- compiler/rustc_codegen_ssa/src/traits/asm.rs | 21 ++++++++++--- 9 files changed, 110 insertions(+), 57 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/global_asm.rs b/compiler/rustc_codegen_cranelift/src/global_asm.rs index 769c008c13f75..335b5a2f2f142 100644 --- a/compiler/rustc_codegen_cranelift/src/global_asm.rs +++ b/compiler/rustc_codegen_cranelift/src/global_asm.rs @@ -107,8 +107,14 @@ fn codegen_global_asm_inner<'tcx>( InlineAsmTemplatePiece::Placeholder { operand_idx, modifier: _, span } => { use rustc_codegen_ssa::back::symbol_export::escape_symbol_name; match operands[operand_idx] { - GlobalAsmOperandRef::Const { ref string } => { - global_asm.push_str(string); + GlobalAsmOperandRef::Const { value, ty } => { + let string = rustc_codegen_ssa::common::asm_const_to_str( + tcx, + span, + value, + FullyMonomorphizedLayoutCx(tcx).layout_of(ty), + ); + global_asm.push_str(&string); } GlobalAsmOperandRef::SymFn { instance } => { if cfg!(not(feature = "inline_asm_sym")) { diff --git a/compiler/rustc_codegen_cranelift/src/inline_asm.rs b/compiler/rustc_codegen_cranelift/src/inline_asm.rs index cc849759754ee..4c663648b8ca3 100644 --- a/compiler/rustc_codegen_cranelift/src/inline_asm.rs +++ b/compiler/rustc_codegen_cranelift/src/inline_asm.rs @@ -96,10 +96,18 @@ pub(crate) fn codegen_inline_asm_terminator<'tcx>( } InlineAsmOperand::Const { ref value } => { let (const_value, ty) = crate::constant::eval_mir_constant(fx, value); + let mir::ConstValue::Scalar(scalar) = const_value else { + span_bug!( + span, + "expected Scalar for promoted asm const, but got {:#?}", + const_value + ) + }; + let value = rustc_codegen_ssa::common::asm_const_to_str( fx.tcx, span, - const_value, + scalar, fx.layout_of(ty), ); CInlineAsmOperand::Const { value } diff --git a/compiler/rustc_codegen_gcc/src/asm.rs b/compiler/rustc_codegen_gcc/src/asm.rs index 6fd7188f656c5..d11ed50e26707 100644 --- a/compiler/rustc_codegen_gcc/src/asm.rs +++ b/compiler/rustc_codegen_gcc/src/asm.rs @@ -12,6 +12,7 @@ use rustc_codegen_ssa::traits::{ }; use rustc_middle::bug; use rustc_middle::ty::Instance; +use rustc_middle::ty::layout::LayoutOf; use rustc_span::{DUMMY_SP, Span}; use rustc_target::asm::*; @@ -303,8 +304,9 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { } } - InlineAsmOperandRef::Const { ref string } => { - constants_len += string.len() + att_dialect as usize; + InlineAsmOperandRef::Const { .. } => { + // We don't know the size at this point, just some estimate. + constants_len += 20; } InlineAsmOperandRef::SymFn { instance } => { @@ -453,7 +455,7 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { template_str.push_str(escaped_char); } } - InlineAsmTemplatePiece::Placeholder { operand_idx, modifier, span: _ } => { + InlineAsmTemplatePiece::Placeholder { operand_idx, modifier, span } => { let mut push_to_template = |modifier, gcc_idx| { use std::fmt::Write; @@ -511,8 +513,15 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { template_str.push_str(name); } - InlineAsmOperandRef::Const { ref string } => { - template_str.push_str(string); + InlineAsmOperandRef::Const { value, ty } => { + // Const operands get injected directly into the template + let string = rustc_codegen_ssa::common::asm_const_to_str( + self.tcx, + span, + value, + self.layout_of(ty), + ); + template_str.push_str(&string); } InlineAsmOperandRef::Label { label } => { @@ -933,13 +942,19 @@ impl<'gcc, 'tcx> AsmCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { .unwrap_or(string.len()); } } - InlineAsmTemplatePiece::Placeholder { operand_idx, modifier: _, span: _ } => { + InlineAsmTemplatePiece::Placeholder { operand_idx, modifier: _, span } => { match operands[operand_idx] { - GlobalAsmOperandRef::Const { ref string } => { + GlobalAsmOperandRef::Const { value, ty } => { // Const operands get injected directly into the // template. Note that we don't need to escape % // here unlike normal inline assembly. - template_str.push_str(string); + let string = rustc_codegen_ssa::common::asm_const_to_str( + self.tcx, + span, + value, + self.layout_of(ty), + ); + template_str.push_str(&string); } GlobalAsmOperandRef::SymFn { instance } => { diff --git a/compiler/rustc_codegen_llvm/src/asm.rs b/compiler/rustc_codegen_llvm/src/asm.rs index 8f192a5a73b63..a7ed218809318 100644 --- a/compiler/rustc_codegen_llvm/src/asm.rs +++ b/compiler/rustc_codegen_llvm/src/asm.rs @@ -189,7 +189,7 @@ impl<'ll, 'tcx> AsmBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { template_str.push_str(s) } } - InlineAsmTemplatePiece::Placeholder { operand_idx, modifier, span: _ } => { + InlineAsmTemplatePiece::Placeholder { operand_idx, modifier, span } => { match operands[operand_idx] { InlineAsmOperandRef::In { reg, .. } | InlineAsmOperandRef::Out { reg, .. } @@ -204,9 +204,15 @@ impl<'ll, 'tcx> AsmBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { template_str.push_str(&format!("${{{}}}", op_idx[&operand_idx])); } } - InlineAsmOperandRef::Const { ref string } => { + InlineAsmOperandRef::Const { value, ty } => { // Const operands get injected directly into the template - template_str.push_str(string); + let string = rustc_codegen_ssa::common::asm_const_to_str( + self.tcx, + span, + value, + self.layout_of(ty), + ); + template_str.push_str(&string); } InlineAsmOperandRef::SymFn { .. } | InlineAsmOperandRef::SymStatic { .. } => { @@ -405,11 +411,17 @@ impl<'tcx> AsmCodegenMethods<'tcx> for CodegenCx<'_, 'tcx> { InlineAsmTemplatePiece::Placeholder { operand_idx, modifier: _, span } => { use rustc_codegen_ssa::back::symbol_export::escape_symbol_name; match operands[operand_idx] { - GlobalAsmOperandRef::Const { ref string } => { + GlobalAsmOperandRef::Const { value, ty } => { // Const operands get injected directly into the // template. Note that we don't need to escape $ // here unlike normal inline assembly. - template_str.push_str(string); + let string = rustc_codegen_ssa::common::asm_const_to_str( + self.tcx, + span, + value, + self.layout_of(ty), + ); + template_str.push_str(&string); } GlobalAsmOperandRef::SymFn { instance } => { let llval = self.get_fn(instance); diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index ae049cfc37f95..c3abde21f3f5b 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -22,12 +22,12 @@ use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile; use rustc_middle::middle::dependency_format::{Dependencies, Linkage}; use rustc_middle::middle::exported_symbols::{self, SymbolExportKind}; use rustc_middle::middle::lang_items; -use rustc_middle::mir::BinOp; -use rustc_middle::mir::interpret::ErrorHandled; +use rustc_middle::mir::interpret::{ErrorHandled, Scalar}; +use rustc_middle::mir::{BinOp, ConstValue}; use rustc_middle::mono::{CodegenUnit, CodegenUnitNameBuilder, MonoItem, MonoItemPartitions}; use rustc_middle::query::Providers; use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, LayoutOf, TyAndLayout}; -use rustc_middle::ty::{self, Instance, PatternKind, Ty, TyCtxt, Unnormalized}; +use rustc_middle::ty::{self, Instance, PatternKind, Ty, TyCtxt, UintTy, Unnormalized}; use rustc_middle::{bug, span_bug}; use rustc_session::Session; use rustc_session::config::{self, CrateType, EntryFnType}; @@ -419,20 +419,23 @@ where Ok(const_value) => { let ty = cx.tcx().typeck_body(anon_const.body).node_type(anon_const.hir_id); - let string = common::asm_const_to_str( - cx.tcx(), - *op_sp, - const_value, - cx.layout_of(ty), - ); - GlobalAsmOperandRef::Const { string } + let ConstValue::Scalar(scalar) = const_value else { + span_bug!( + *op_sp, + "expected Scalar for promoted asm const, but got {:#?}", + const_value + ) + }; + GlobalAsmOperandRef::Const { value: scalar, ty } } Err(ErrorHandled::Reported { .. }) => { // An error has already been reported and // compilation is guaranteed to fail if execution - // hits this path. So an empty string instead of - // a stringified constant value will suffice. - GlobalAsmOperandRef::Const { string: String::new() } + // hits this path. So anything will suffice. + GlobalAsmOperandRef::Const { + value: Scalar::from_u32(0), + ty: Ty::new_uint(cx.tcx(), UintTy::U32), + } } Err(ErrorHandled::TooGeneric(_)) => { span_bug!(*op_sp, "asm const cannot be resolved; too generic") diff --git a/compiler/rustc_codegen_ssa/src/common.rs b/compiler/rustc_codegen_ssa/src/common.rs index ae72258a87c86..55cb6febdc383 100644 --- a/compiler/rustc_codegen_ssa/src/common.rs +++ b/compiler/rustc_codegen_ssa/src/common.rs @@ -2,9 +2,10 @@ use rustc_hir::LangItem; use rustc_hir::attrs::PeImportNameType; +use rustc_middle::mir::interpret::Scalar; use rustc_middle::ty::layout::TyAndLayout; use rustc_middle::ty::{self, Instance, TyCtxt}; -use rustc_middle::{bug, mir, span_bug}; +use rustc_middle::{bug, span_bug}; use rustc_session::cstore::{DllCallingConvention, DllImport, DllImportSymbolType}; use rustc_span::Span; use rustc_target::spec::{CfgAbi, Env, Os, Target}; @@ -153,12 +154,9 @@ pub(crate) fn shift_mask_val<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( pub fn asm_const_to_str<'tcx>( tcx: TyCtxt<'tcx>, sp: Span, - const_value: mir::ConstValue, + scalar: Scalar, ty_and_layout: TyAndLayout<'tcx>, ) -> String { - let mir::ConstValue::Scalar(scalar) = const_value else { - span_bug!(sp, "expected Scalar for promoted asm const, but got {:#?}", const_value) - }; let value = scalar.assert_scalar_int().to_bits(ty_and_layout.size); match ty_and_layout.ty.kind() { ty::Uint(_) => value.to_string(), diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 99075536d04a8..cb919c8ab9c13 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -1479,13 +1479,14 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } mir::InlineAsmOperand::Const { ref value } => { let const_value = self.eval_mir_constant(value); - let string = common::asm_const_to_str( - bx.tcx(), - span, - const_value, - bx.layout_of(value.ty()), - ); - InlineAsmOperandRef::Const { string } + let mir::ConstValue::Scalar(scalar) = const_value else { + span_bug!( + span, + "expected Scalar for promoted asm const, but got {:#?}", + const_value + ) + }; + InlineAsmOperandRef::Const { value: scalar, ty: value.ty() } } mir::InlineAsmOperand::SymFn { ref value } => { let const_ = self.monomorphize(value.const_); diff --git a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs index c33c228cc5b2d..61079755a268a 100644 --- a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs +++ b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs @@ -1,16 +1,15 @@ use rustc_abi::{BackendRepr, Float, Integer, Primitive, RegKind}; use rustc_hir::attrs::{InstructionSetAttr, Linkage}; use rustc_hir::def_id::LOCAL_CRATE; -use rustc_middle::mir::{InlineAsmOperand, START_BLOCK}; +use rustc_middle::mir::{self, InlineAsmOperand, START_BLOCK}; use rustc_middle::mono::{MonoItemData, Visibility}; use rustc_middle::ty::layout::{FnAbiOf, LayoutOf, TyAndLayout}; use rustc_middle::ty::{Instance, Ty, TyCtxt, TypeVisitableExt}; -use rustc_middle::{bug, ty}; +use rustc_middle::{bug, span_bug, ty}; use rustc_span::sym; use rustc_target::callconv::{ArgAbi, FnAbi, PassMode}; use rustc_target::spec::{Arch, BinaryFormat, Env, Os}; -use crate::common; use crate::mir::AsmCodegenMethods; use crate::traits::GlobalAsmOperandRef; @@ -77,15 +76,15 @@ fn inline_to_global_operand<'a, 'tcx, Cx: LayoutOf<'tcx, LayoutOfResult = TyAndL cx.typing_env(), ty::EarlyBinder::bind(cx.tcx(), value.ty()), ); + let mir::ConstValue::Scalar(scalar) = const_value else { + span_bug!( + value.span, + "expected Scalar for promoted asm const, but got {:#?}", + const_value + ) + }; - let string = common::asm_const_to_str( - cx.tcx(), - value.span, - const_value, - cx.layout_of(mono_type), - ); - - GlobalAsmOperandRef::Const { string } + GlobalAsmOperandRef::Const { value: scalar, ty: mono_type } } InlineAsmOperand::SymFn { value } => { let mono_type = instance.instantiate_mir_and_normalize_erasing_regions( diff --git a/compiler/rustc_codegen_ssa/src/traits/asm.rs b/compiler/rustc_codegen_ssa/src/traits/asm.rs index cc7a6a3f19e9e..f7bab1e07c3a9 100644 --- a/compiler/rustc_codegen_ssa/src/traits/asm.rs +++ b/compiler/rustc_codegen_ssa/src/traits/asm.rs @@ -1,6 +1,7 @@ use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_hir::def_id::DefId; -use rustc_middle::ty::Instance; +use rustc_middle::mir::interpret::Scalar; +use rustc_middle::ty::{Instance, Ty}; use rustc_span::Span; use rustc_target::asm::InlineAsmRegOrRegClass; @@ -26,7 +27,9 @@ pub enum InlineAsmOperandRef<'tcx, B: BackendTypes + ?Sized> { out_place: Option>, }, Const { - string: String, + value: Scalar, + /// Type of the constant. This is needed to extract width and signedness. + ty: Ty<'tcx>, }, SymFn { instance: Instance<'tcx>, @@ -41,9 +44,17 @@ pub enum InlineAsmOperandRef<'tcx, B: BackendTypes + ?Sized> { #[derive(Debug)] pub enum GlobalAsmOperandRef<'tcx> { - Const { string: String }, - SymFn { instance: Instance<'tcx> }, - SymStatic { def_id: DefId }, + Const { + value: Scalar, + /// Type of the constant. This is needed to extract width and signedness. + ty: Ty<'tcx>, + }, + SymFn { + instance: Instance<'tcx>, + }, + SymStatic { + def_id: DefId, + }, } pub trait AsmBuilderMethods<'tcx>: BackendTypes { From ca4033987ee3061bf4f016e35bacb3da7c4cd7b7 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 18 Dec 2025 18:31:04 +0000 Subject: [PATCH 25/61] Unify handling of asm const and sym using CTFE This gives the asm-const code the basic ability to deal wiht pointer and provenances, which lays the ground work for asm_const_ptr. Note that `SymStatic` is not fully removed, a specialized is kept and renamed as `SymThreadLocalStatic`, for `#[thread_local]` statics where CTFE does not support naming. The `#[thread_local]` is unstable feature and it's not clear if we want to support this in `sym`, but removal of it should be a separate PR. --- .../rustc_codegen_cranelift/src/global_asm.rs | 69 ++++--- .../rustc_codegen_cranelift/src/inline_asm.rs | 2 +- compiler/rustc_codegen_gcc/src/asm.rs | 169 +++++++++++------- compiler/rustc_codegen_llvm/src/asm.rs | 132 ++++++++++---- compiler/rustc_codegen_ssa/src/base.rs | 22 ++- compiler/rustc_codegen_ssa/src/common.rs | 7 +- compiler/rustc_codegen_ssa/src/mir/block.rs | 22 ++- .../rustc_codegen_ssa/src/mir/naked_asm.rs | 21 ++- compiler/rustc_codegen_ssa/src/traits/asm.rs | 10 +- 9 files changed, 310 insertions(+), 144 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/global_asm.rs b/compiler/rustc_codegen_cranelift/src/global_asm.rs index 335b5a2f2f142..558d3cac8e754 100644 --- a/compiler/rustc_codegen_cranelift/src/global_asm.rs +++ b/compiler/rustc_codegen_cranelift/src/global_asm.rs @@ -7,6 +7,7 @@ use std::process::{Command, Stdio}; use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_codegen_ssa::traits::{AsmCodegenMethods, GlobalAsmOperandRef}; +use rustc_middle::mir::interpret::{GlobalAlloc, Scalar as ConstScalar}; use rustc_middle::ty::TyCtxt; use rustc_middle::ty::layout::{ FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasTyCtxt, HasTypingEnv, LayoutError, LayoutOfHelpers, @@ -108,34 +109,52 @@ fn codegen_global_asm_inner<'tcx>( use rustc_codegen_ssa::back::symbol_export::escape_symbol_name; match operands[operand_idx] { GlobalAsmOperandRef::Const { value, ty } => { - let string = rustc_codegen_ssa::common::asm_const_to_str( - tcx, - span, - value, - FullyMonomorphizedLayoutCx(tcx).layout_of(ty), - ); - global_asm.push_str(&string); - } - GlobalAsmOperandRef::SymFn { instance } => { - if cfg!(not(feature = "inline_asm_sym")) { - tcx.dcx().span_err( - span, - "asm! and global_asm! sym operands are not yet supported", - ); - } + match value { + ConstScalar::Int(int) => { + let string = rustc_codegen_ssa::common::asm_const_to_str( + tcx, + span, + int, + FullyMonomorphizedLayoutCx(tcx).layout_of(ty), + ); + global_asm.push_str(&string); + } - let symbol = tcx.symbol_name(instance); - let symbol_name = if tcx.sess.target.is_like_darwin { - format!("_{}", symbol.name) - } else { - symbol.name.to_owned() - }; + ConstScalar::Ptr(ptr, _) => { + if cfg!(not(feature = "inline_asm_sym")) { + tcx.dcx().span_err( + span, + "asm! and global_asm! sym operands are not yet supported", + ); + } - // FIXME handle the case where the function was made private to the - // current codegen unit - global_asm.push_str(&escape_symbol_name(tcx, &symbol_name, span)); + let (prov, offset) = ptr.prov_and_relative_offset(); + assert_eq!(offset.bytes(), 0); + let global_alloc = tcx.global_alloc(prov.alloc_id()); + let symbol = match global_alloc { + GlobalAlloc::Function { instance } => { + // FIXME handle the case where the function was made private to the + // current codegen unit + tcx.symbol_name(instance) + } + GlobalAlloc::Static(def_id) => { + let instance = Instance::mono(tcx, def_id); + tcx.symbol_name(instance) + } + GlobalAlloc::Memory(_) + | GlobalAlloc::VTable(..) + | GlobalAlloc::TypeId { .. } => unreachable!(), + }; + let symbol_name = if tcx.sess.target.is_like_darwin { + format!("_{}", symbol.name) + } else { + symbol.name.to_owned() + }; + global_asm.push_str(&escape_symbol_name(tcx, &symbol_name, span)); + } + } } - GlobalAsmOperandRef::SymStatic { def_id } => { + GlobalAsmOperandRef::SymThreadLocalStatic { def_id } => { if cfg!(not(feature = "inline_asm_sym")) { tcx.dcx().span_err( span, diff --git a/compiler/rustc_codegen_cranelift/src/inline_asm.rs b/compiler/rustc_codegen_cranelift/src/inline_asm.rs index 4c663648b8ca3..8b9662aabf812 100644 --- a/compiler/rustc_codegen_cranelift/src/inline_asm.rs +++ b/compiler/rustc_codegen_cranelift/src/inline_asm.rs @@ -107,7 +107,7 @@ pub(crate) fn codegen_inline_asm_terminator<'tcx>( let value = rustc_codegen_ssa::common::asm_const_to_str( fx.tcx, span, - scalar, + scalar.assert_scalar_int(), fx.layout_of(ty), ); CInlineAsmOperand::Const { value } diff --git a/compiler/rustc_codegen_gcc/src/asm.rs b/compiler/rustc_codegen_gcc/src/asm.rs index d11ed50e26707..a1321a681a0d7 100644 --- a/compiler/rustc_codegen_gcc/src/asm.rs +++ b/compiler/rustc_codegen_gcc/src/asm.rs @@ -11,6 +11,7 @@ use rustc_codegen_ssa::traits::{ GlobalAsmOperandRef, InlineAsmOperandRef, }; use rustc_middle::bug; +use rustc_middle::mir::interpret::{GlobalAlloc, Scalar}; use rustc_middle::ty::Instance; use rustc_middle::ty::layout::LayoutOf; use rustc_span::{DUMMY_SP, Span}; @@ -309,13 +310,7 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { constants_len += 20; } - InlineAsmOperandRef::SymFn { instance } => { - // FIXME(@Amanieu): Additional mangling is needed on - // some targets to add a leading underscore (Mach-O) - // or byte count suffixes (x86 Windows). - constants_len += self.tcx.symbol_name(instance).name.len(); - } - InlineAsmOperandRef::SymStatic { def_id } => { + InlineAsmOperandRef::SymThreadLocalStatic { def_id } => { // FIXME(@Amanieu): Additional mangling is needed on // some targets to add a leading underscore (Mach-O). constants_len += @@ -404,24 +399,32 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { // processed in the previous pass } - InlineAsmOperandRef::SymFn { instance } => { - inputs.push(AsmInOperand { - constraint: "X".into(), - rust_idx, - val: get_fn(self.cx, instance).get_address(None), - }); - } - - InlineAsmOperandRef::SymStatic { def_id } => { - inputs.push(AsmInOperand { - constraint: "X".into(), - rust_idx, - val: self.cx.get_static(def_id).get_address(None), - }); - } + InlineAsmOperandRef::Const { value, ty: _ } => match value { + Scalar::Int(_) => (), + Scalar::Ptr(ptr, _) => { + let (prov, offset) = ptr.prov_and_relative_offset(); + assert_eq!(offset.bytes(), 0); + let global_alloc = self.tcx.global_alloc(prov.alloc_id()); + let val = match global_alloc { + GlobalAlloc::Function { instance } => { + get_fn(self.cx, instance).get_address(None) + } + GlobalAlloc::Static(def_id) => { + self.cx.get_static(def_id).get_address(None) + } + GlobalAlloc::Memory(_) + | GlobalAlloc::VTable(..) + | GlobalAlloc::TypeId { .. } => unreachable!(), + }; + inputs.push(AsmInOperand { constraint: "X".into(), rust_idx, val }); + } + }, - InlineAsmOperandRef::Const { .. } => { - // processed in the previous pass + InlineAsmOperandRef::SymThreadLocalStatic { def_id } => { + // FIXME(@Amanieu): Additional mangling is needed on + // some targets to add a leading underscore (MachO). + constants_len += + self.tcx.symbol_name(Instance::mono(self.tcx, def_id)).name.len(); } InlineAsmOperandRef::Label { .. } => { @@ -497,15 +500,46 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { push_to_template(modifier, gcc_index); } - InlineAsmOperandRef::SymFn { instance } => { - // FIXME(@Amanieu): Additional mangling is needed on - // some targets to add a leading underscore (Mach-O) - // or byte count suffixes (x86 Windows). - let name = self.tcx.symbol_name(instance).name; - template_str.push_str(name); + InlineAsmOperandRef::Const { value, ty } => { + match value { + Scalar::Int(int) => { + // Const operands get injected directly into the template + let string = rustc_codegen_ssa::common::asm_const_to_str( + self.tcx, + span, + int, + self.layout_of(ty), + ); + template_str.push_str(&string); + } + + Scalar::Ptr(ptr, _) => { + let (prov, offset) = ptr.prov_and_relative_offset(); + assert_eq!(offset.bytes(), 0); + let global_alloc = self.tcx.global_alloc(prov.alloc_id()); + let symbol_name = match global_alloc { + GlobalAlloc::Function { instance } => { + // FIXME(@Amanieu): Additional mangling is needed on + // some targets to add a leading underscore (Mach-O) + // or byte count suffixes (x86 Windows). + self.tcx.symbol_name(instance) + } + GlobalAlloc::Static(def_id) => { + // FIXME(@Amanieu): Additional mangling is needed on + // some targets to add a leading underscore (Mach-O). + let instance = Instance::mono(self.tcx, def_id); + self.tcx.symbol_name(instance) + } + GlobalAlloc::Memory(_) + | GlobalAlloc::VTable(..) + | GlobalAlloc::TypeId { .. } => unreachable!(), + }; + template_str.push_str(symbol_name.name); + } + } } - InlineAsmOperandRef::SymStatic { def_id } => { + InlineAsmOperandRef::SymThreadLocalStatic { def_id } => { // FIXME(@Amanieu): Additional mangling is needed on // some targets to add a leading underscore (Mach-O). let instance = Instance::mono(self.tcx, def_id); @@ -513,17 +547,6 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { template_str.push_str(name); } - InlineAsmOperandRef::Const { value, ty } => { - // Const operands get injected directly into the template - let string = rustc_codegen_ssa::common::asm_const_to_str( - self.tcx, - span, - value, - self.layout_of(ty), - ); - template_str.push_str(&string); - } - InlineAsmOperandRef::Label { label } => { let label_gcc_index = labels.iter().position(|&l| l == label).expect("wrong rust index"); @@ -945,29 +968,49 @@ impl<'gcc, 'tcx> AsmCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { InlineAsmTemplatePiece::Placeholder { operand_idx, modifier: _, span } => { match operands[operand_idx] { GlobalAsmOperandRef::Const { value, ty } => { - // Const operands get injected directly into the - // template. Note that we don't need to escape % - // here unlike normal inline assembly. - let string = rustc_codegen_ssa::common::asm_const_to_str( - self.tcx, - span, - value, - self.layout_of(ty), - ); - template_str.push_str(&string); - } + match value { + Scalar::Int(int) => { + // Const operands get injected directly into the + // template. Note that we don't need to escape % + // here unlike normal inline assembly. + let string = rustc_codegen_ssa::common::asm_const_to_str( + self.tcx, + span, + int, + self.layout_of(ty), + ); + template_str.push_str(&string); + } - GlobalAsmOperandRef::SymFn { instance } => { - let function = get_fn(self, instance); - self.add_used_function(function); - // FIXME(@Amanieu): Additional mangling is needed on - // some targets to add a leading underscore (Mach-O) - // or byte count suffixes (x86 Windows). - let name = self.tcx.symbol_name(instance).name; - template_str.push_str(name); + Scalar::Ptr(ptr, _) => { + let (prov, offset) = ptr.prov_and_relative_offset(); + assert_eq!(offset.bytes(), 0); + let global_alloc = self.tcx.global_alloc(prov.alloc_id()); + let symbol_name = match global_alloc { + GlobalAlloc::Function { instance } => { + let function = get_fn(self, instance); + self.add_used_function(function); + // FIXME(@Amanieu): Additional mangling is needed on + // some targets to add a leading underscore (Mach-O) + // or byte count suffixes (x86 Windows). + self.tcx.symbol_name(instance) + } + GlobalAlloc::Static(def_id) => { + // FIXME(antoyo): set the global variable as used. + // FIXME(@Amanieu): Additional mangling is needed on + // some targets to add a leading underscore (Mach-O). + let instance = Instance::mono(self.tcx, def_id); + self.tcx.symbol_name(instance) + } + GlobalAlloc::Memory(_) + | GlobalAlloc::VTable(..) + | GlobalAlloc::TypeId { .. } => unreachable!(), + }; + template_str.push_str(symbol_name.name); + } + } } - - GlobalAsmOperandRef::SymStatic { def_id } => { + GlobalAsmOperandRef::SymThreadLocalStatic { def_id } => { // FIXME(antoyo): set the global variable as used. // FIXME(@Amanieu): Additional mangling is needed on // some targets to add a leading underscore (Mach-O). diff --git a/compiler/rustc_codegen_llvm/src/asm.rs b/compiler/rustc_codegen_llvm/src/asm.rs index a7ed218809318..339347b366d88 100644 --- a/compiler/rustc_codegen_llvm/src/asm.rs +++ b/compiler/rustc_codegen_llvm/src/asm.rs @@ -5,6 +5,7 @@ use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_codegen_ssa::mir::operand::OperandValue; use rustc_codegen_ssa::traits::*; use rustc_data_structures::fx::FxHashMap; +use rustc_middle::mir::interpret::{GlobalAlloc, Scalar as ConstScalar}; use rustc_middle::ty::Instance; use rustc_middle::ty::layout::TyAndLayout; use rustc_middle::{bug, span_bug}; @@ -157,12 +158,30 @@ impl<'ll, 'tcx> AsmBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { constraints.push(format!("{}", op_idx[&idx])); } } - InlineAsmOperandRef::SymFn { instance } => { - inputs.push(self.cx.get_fn(instance)); - op_idx.insert(idx, constraints.len()); - constraints.push("s".to_string()); - } - InlineAsmOperandRef::SymStatic { def_id } => { + InlineAsmOperandRef::Const { value, ty: _ } => match value { + ConstScalar::Int(_) => (), + ConstScalar::Ptr(ptr, _) => { + let (prov, offset) = ptr.prov_and_relative_offset(); + assert_eq!(offset.bytes(), 0); + let global_alloc = self.tcx.global_alloc(prov.alloc_id()); + match global_alloc { + GlobalAlloc::Function { instance } => { + inputs.push(self.cx.get_fn(instance)); + op_idx.insert(idx, constraints.len()); + constraints.push("s".to_string()); + } + GlobalAlloc::Static(def_id) => { + inputs.push(self.cx.get_static(def_id)); + op_idx.insert(idx, constraints.len()); + constraints.push("s".to_string()); + } + GlobalAlloc::Memory(_) + | GlobalAlloc::VTable(..) + | GlobalAlloc::TypeId { .. } => unreachable!(), + } + } + }, + InlineAsmOperandRef::SymThreadLocalStatic { def_id } => { inputs.push(self.cx.get_static(def_id)); op_idx.insert(idx, constraints.len()); constraints.push("s".to_string()); @@ -205,17 +224,37 @@ impl<'ll, 'tcx> AsmBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { } } InlineAsmOperandRef::Const { value, ty } => { - // Const operands get injected directly into the template - let string = rustc_codegen_ssa::common::asm_const_to_str( - self.tcx, - span, - value, - self.layout_of(ty), - ); - template_str.push_str(&string); + match value { + ConstScalar::Int(int) => { + // Const operands get injected directly into the template + let string = rustc_codegen_ssa::common::asm_const_to_str( + self.tcx, + span, + int, + self.layout_of(ty), + ); + template_str.push_str(&string); + } + ConstScalar::Ptr(ptr, _) => { + let (prov, offset) = ptr.prov_and_relative_offset(); + assert_eq!(offset.bytes(), 0); + let global_alloc = self.tcx.global_alloc(prov.alloc_id()); + match global_alloc { + GlobalAlloc::Function { .. } | GlobalAlloc::Static(_) => { + // Only emit the raw symbol name + template_str.push_str(&format!( + "${{{}:c}}", + op_idx[&operand_idx] + )); + } + GlobalAlloc::Memory(_) + | GlobalAlloc::VTable(..) + | GlobalAlloc::TypeId { .. } => unreachable!(), + } + } + } } - InlineAsmOperandRef::SymFn { .. } - | InlineAsmOperandRef::SymStatic { .. } => { + InlineAsmOperandRef::SymThreadLocalStatic { .. } => { // Only emit the raw symbol name template_str.push_str(&format!("${{{}:c}}", op_idx[&operand_idx])); } @@ -412,27 +451,48 @@ impl<'tcx> AsmCodegenMethods<'tcx> for CodegenCx<'_, 'tcx> { use rustc_codegen_ssa::back::symbol_export::escape_symbol_name; match operands[operand_idx] { GlobalAsmOperandRef::Const { value, ty } => { - // Const operands get injected directly into the - // template. Note that we don't need to escape $ - // here unlike normal inline assembly. - let string = rustc_codegen_ssa::common::asm_const_to_str( - self.tcx, - span, - value, - self.layout_of(ty), - ); - template_str.push_str(&string); - } - GlobalAsmOperandRef::SymFn { instance } => { - let llval = self.get_fn(instance); - self.add_compiler_used_global(llval); - let symbol = llvm::build_string(|s| unsafe { - llvm::LLVMRustGetMangledName(llval, s); - }) - .expect("symbol is not valid UTF-8"); - template_str.push_str(&escape_symbol_name(self.tcx, &symbol, span)); + match value { + ConstScalar::Int(int) => { + // Const operands get injected directly into the + // template. Note that we don't need to escape $ + // here unlike normal inline assembly. + let string = rustc_codegen_ssa::common::asm_const_to_str( + self.tcx, + span, + int, + self.layout_of(ty), + ); + template_str.push_str(&string); + } + + ConstScalar::Ptr(ptr, _) => { + let (prov, offset) = ptr.prov_and_relative_offset(); + assert_eq!(offset.bytes(), 0); + let global_alloc = self.tcx.global_alloc(prov.alloc_id()); + let llval = match global_alloc { + GlobalAlloc::Function { instance } => self.get_fn(instance), + GlobalAlloc::Static(def_id) => self + .renamed_statics + .borrow() + .get(&def_id) + .copied() + .unwrap_or_else(|| self.get_static(def_id)), + GlobalAlloc::Memory(_) + | GlobalAlloc::VTable(..) + | GlobalAlloc::TypeId { .. } => unreachable!(), + }; + + self.add_compiler_used_global(llval); + let symbol = llvm::build_string(|s| unsafe { + llvm::LLVMRustGetMangledName(llval, s); + }) + .expect("symbol is not valid UTF-8"); + template_str + .push_str(&escape_symbol_name(self.tcx, &symbol, span)); + } + } } - GlobalAsmOperandRef::SymStatic { def_id } => { + GlobalAsmOperandRef::SymThreadLocalStatic { def_id } => { let llval = self .renamed_statics .borrow() diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index c3abde21f3f5b..a409cd5002eb4 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -22,7 +22,7 @@ use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile; use rustc_middle::middle::dependency_format::{Dependencies, Linkage}; use rustc_middle::middle::exported_symbols::{self, SymbolExportKind}; use rustc_middle::middle::lang_items; -use rustc_middle::mir::interpret::{ErrorHandled, Scalar}; +use rustc_middle::mir::interpret::{CTFE_ALLOC_SALT, ErrorHandled, Scalar}; use rustc_middle::mir::{BinOp, ConstValue}; use rustc_middle::mono::{CodegenUnit, CodegenUnitNameBuilder, MonoItem, MonoItemPartitions}; use rustc_middle::query::Providers; @@ -455,10 +455,26 @@ where _ => span_bug!(*op_sp, "asm sym is not a function"), }; - GlobalAsmOperandRef::SymFn { instance } + GlobalAsmOperandRef::Const { + value: Scalar::from_pointer( + cx.tcx().reserve_and_set_fn_alloc(instance, CTFE_ALLOC_SALT).into(), + cx, + ), + ty: Ty::new_fn_ptr(cx.tcx(), ty.fn_sig(cx.tcx())), + } } rustc_hir::InlineAsmOperand::SymStatic { path: _, def_id } => { - GlobalAsmOperandRef::SymStatic { def_id } + if cx.tcx().is_thread_local_static(def_id) { + GlobalAsmOperandRef::SymThreadLocalStatic { def_id } + } else { + GlobalAsmOperandRef::Const { + value: Scalar::from_pointer( + cx.tcx().reserve_and_set_static_alloc(def_id).into(), + cx, + ), + ty: cx.tcx().static_ptr_ty(def_id, cx.typing_env()), + } + } } rustc_hir::InlineAsmOperand::In { .. } | rustc_hir::InlineAsmOperand::Out { .. } diff --git a/compiler/rustc_codegen_ssa/src/common.rs b/compiler/rustc_codegen_ssa/src/common.rs index 55cb6febdc383..f9b904ede9833 100644 --- a/compiler/rustc_codegen_ssa/src/common.rs +++ b/compiler/rustc_codegen_ssa/src/common.rs @@ -2,9 +2,8 @@ use rustc_hir::LangItem; use rustc_hir::attrs::PeImportNameType; -use rustc_middle::mir::interpret::Scalar; use rustc_middle::ty::layout::TyAndLayout; -use rustc_middle::ty::{self, Instance, TyCtxt}; +use rustc_middle::ty::{self, Instance, ScalarInt, TyCtxt}; use rustc_middle::{bug, span_bug}; use rustc_session::cstore::{DllCallingConvention, DllImport, DllImportSymbolType}; use rustc_span::Span; @@ -154,10 +153,10 @@ pub(crate) fn shift_mask_val<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( pub fn asm_const_to_str<'tcx>( tcx: TyCtxt<'tcx>, sp: Span, - scalar: Scalar, + scalar: ScalarInt, ty_and_layout: TyAndLayout<'tcx>, ) -> String { - let value = scalar.assert_scalar_int().to_bits(ty_and_layout.size); + let value = scalar.to_bits(ty_and_layout.size); match ty_and_layout.ty.kind() { ty::Uint(_) => value.to_string(), ty::Int(int_ty) => match int_ty.normalize(tcx.sess.target.pointer_width) { diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index cb919c8ab9c13..e2c0cd03d85a4 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -10,6 +10,7 @@ use rustc_data_structures::packed::Pu128; use rustc_hir::attrs::AttributeKind; use rustc_hir::lang_items::LangItem; use rustc_lint_defs::builtin::TAIL_CALL_TRACK_CALLER; +use rustc_middle::mir::interpret::{CTFE_ALLOC_SALT, Scalar}; use rustc_middle::mir::{self, AssertKind, InlineAsmMacro, SwitchTargets, UnwindTerminateReason}; use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, ValidityRequirement}; use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths}; @@ -1498,13 +1499,30 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { args.no_bound_vars().unwrap(), ) .unwrap(); - InlineAsmOperandRef::SymFn { instance } + + InlineAsmOperandRef::Const { + value: Scalar::from_pointer( + bx.tcx().reserve_and_set_fn_alloc(instance, CTFE_ALLOC_SALT).into(), + bx, + ), + ty: Ty::new_fn_ptr(bx.tcx(), const_.ty().fn_sig(bx.tcx())), + } } else { span_bug!(span, "invalid type for asm sym (fn)"); } } mir::InlineAsmOperand::SymStatic { def_id } => { - InlineAsmOperandRef::SymStatic { def_id } + if bx.tcx().is_thread_local_static(def_id) { + InlineAsmOperandRef::SymThreadLocalStatic { def_id } + } else { + InlineAsmOperandRef::Const { + value: Scalar::from_pointer( + bx.tcx().reserve_and_set_static_alloc(def_id).into(), + bx, + ), + ty: bx.tcx().static_ptr_ty(def_id, bx.typing_env()), + } + } } mir::InlineAsmOperand::Label { target_index } => { InlineAsmOperandRef::Label { label: self.llbb(targets[target_index]) } diff --git a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs index 61079755a268a..416308c80445d 100644 --- a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs +++ b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs @@ -1,6 +1,7 @@ use rustc_abi::{BackendRepr, Float, Integer, Primitive, RegKind}; use rustc_hir::attrs::{InstructionSetAttr, Linkage}; use rustc_hir::def_id::LOCAL_CRATE; +use rustc_middle::mir::interpret::{CTFE_ALLOC_SALT, Scalar}; use rustc_middle::mir::{self, InlineAsmOperand, START_BLOCK}; use rustc_middle::mono::{MonoItemData, Visibility}; use rustc_middle::ty::layout::{FnAbiOf, LayoutOf, TyAndLayout}; @@ -104,10 +105,26 @@ fn inline_to_global_operand<'a, 'tcx, Cx: LayoutOf<'tcx, LayoutOfResult = TyAndL _ => bug!("asm sym is not a function"), }; - GlobalAsmOperandRef::SymFn { instance } + GlobalAsmOperandRef::Const { + value: Scalar::from_pointer( + cx.tcx().reserve_and_set_fn_alloc(instance, CTFE_ALLOC_SALT).into(), + cx, + ), + ty: Ty::new_fn_ptr(cx.tcx(), mono_type.fn_sig(cx.tcx())), + } } InlineAsmOperand::SymStatic { def_id } => { - GlobalAsmOperandRef::SymStatic { def_id: *def_id } + if cx.tcx().is_thread_local_static(*def_id) { + GlobalAsmOperandRef::SymThreadLocalStatic { def_id: *def_id } + } else { + GlobalAsmOperandRef::Const { + value: Scalar::from_pointer( + cx.tcx().reserve_and_set_static_alloc(*def_id).into(), + cx, + ), + ty: cx.tcx().static_ptr_ty(*def_id, cx.typing_env()), + } + } } InlineAsmOperand::In { .. } | InlineAsmOperand::Out { .. } diff --git a/compiler/rustc_codegen_ssa/src/traits/asm.rs b/compiler/rustc_codegen_ssa/src/traits/asm.rs index f7bab1e07c3a9..85a2fe09ba414 100644 --- a/compiler/rustc_codegen_ssa/src/traits/asm.rs +++ b/compiler/rustc_codegen_ssa/src/traits/asm.rs @@ -31,10 +31,7 @@ pub enum InlineAsmOperandRef<'tcx, B: BackendTypes + ?Sized> { /// Type of the constant. This is needed to extract width and signedness. ty: Ty<'tcx>, }, - SymFn { - instance: Instance<'tcx>, - }, - SymStatic { + SymThreadLocalStatic { def_id: DefId, }, Label { @@ -49,10 +46,7 @@ pub enum GlobalAsmOperandRef<'tcx> { /// Type of the constant. This is needed to extract width and signedness. ty: Ty<'tcx>, }, - SymFn { - instance: Instance<'tcx>, - }, - SymStatic { + SymThreadLocalStatic { def_id: DefId, }, } From 4c39d62eb936c06a5d4bc01f7d8ffb7a613bb9e1 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 19 Dec 2025 16:05:00 +0000 Subject: [PATCH 26/61] Unify handling of `GlobalAlloc` inside backend With the previous commit, now we can see there are some code duplication for the handling of `GlobalAlloc` inside backends. Do some clean up to unify them. --- compiler/rustc_codegen_gcc/src/asm.rs | 52 +++----- compiler/rustc_codegen_gcc/src/common.rs | 123 +++++++++++------- compiler/rustc_codegen_llvm/src/asm.rs | 54 ++------ compiler/rustc_codegen_llvm/src/common.rs | 151 ++++++++++++---------- 4 files changed, 183 insertions(+), 197 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/asm.rs b/compiler/rustc_codegen_gcc/src/asm.rs index a1321a681a0d7..90576906c6702 100644 --- a/compiler/rustc_codegen_gcc/src/asm.rs +++ b/compiler/rustc_codegen_gcc/src/asm.rs @@ -145,6 +145,9 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { // Clobbers collected from `out("explicit register") _` and `inout("explicit_reg") var => _` let mut clobbers = vec![]; + // Symbols name that needs to be inserted to asm const ptr template string. + let mut const_syms = vec![]; + // We're trying to preallocate space for the template let mut constants_len = 0; @@ -405,17 +408,8 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { let (prov, offset) = ptr.prov_and_relative_offset(); assert_eq!(offset.bytes(), 0); let global_alloc = self.tcx.global_alloc(prov.alloc_id()); - let val = match global_alloc { - GlobalAlloc::Function { instance } => { - get_fn(self.cx, instance).get_address(None) - } - GlobalAlloc::Static(def_id) => { - self.cx.get_static(def_id).get_address(None) - } - GlobalAlloc::Memory(_) - | GlobalAlloc::VTable(..) - | GlobalAlloc::TypeId { .. } => unreachable!(), - }; + let (val, sym) = self.cx.alloc_to_backend(global_alloc, true).unwrap(); + const_syms.push(sym.unwrap()); inputs.push(AsmInOperand { constraint: "X".into(), rust_idx, val }); } }, @@ -514,27 +508,13 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { } Scalar::Ptr(ptr, _) => { - let (prov, offset) = ptr.prov_and_relative_offset(); + let (_, offset) = ptr.prov_and_relative_offset(); assert_eq!(offset.bytes(), 0); - let global_alloc = self.tcx.global_alloc(prov.alloc_id()); - let symbol_name = match global_alloc { - GlobalAlloc::Function { instance } => { - // FIXME(@Amanieu): Additional mangling is needed on - // some targets to add a leading underscore (Mach-O) - // or byte count suffixes (x86 Windows). - self.tcx.symbol_name(instance) - } - GlobalAlloc::Static(def_id) => { - // FIXME(@Amanieu): Additional mangling is needed on - // some targets to add a leading underscore (Mach-O). - let instance = Instance::mono(self.tcx, def_id); - self.tcx.symbol_name(instance) - } - GlobalAlloc::Memory(_) - | GlobalAlloc::VTable(..) - | GlobalAlloc::TypeId { .. } => unreachable!(), - }; - template_str.push_str(symbol_name.name); + let sym = const_syms.remove(0); + // FIXME(@Amanieu): Additional mangling is needed on + // some targets to add a leading underscore (Mach-O) + // or byte count suffixes (x86 Windows). + template_str.push_str(sym.name); } } } @@ -995,16 +975,14 @@ impl<'gcc, 'tcx> AsmCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { // or byte count suffixes (x86 Windows). self.tcx.symbol_name(instance) } - GlobalAlloc::Static(def_id) => { + _ => { + let (_, syms) = + self.alloc_to_backend(global_alloc, true).unwrap(); // FIXME(antoyo): set the global variable as used. // FIXME(@Amanieu): Additional mangling is needed on // some targets to add a leading underscore (Mach-O). - let instance = Instance::mono(self.tcx, def_id); - self.tcx.symbol_name(instance) + syms.unwrap() } - GlobalAlloc::Memory(_) - | GlobalAlloc::VTable(..) - | GlobalAlloc::TypeId { .. } => unreachable!(), }; template_str.push_str(symbol_name.name); } diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index e73b8aab54d73..d3d9f60128b5c 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -7,6 +7,7 @@ use rustc_codegen_ssa::traits::{ use rustc_middle::mir::Mutability; use rustc_middle::mir::interpret::{GlobalAlloc, PointerArithmetic, Scalar}; use rustc_middle::ty::layout::LayoutOf; +use rustc_middle::ty::{Instance, SymbolName}; use rustc_session::PointerAuthSchema; use crate::consts::const_alloc_to_gcc; @@ -47,6 +48,68 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { // SIMD builtins require a constant value. self.bitcast_if_needed(value, typ) } + + pub(crate) fn alloc_to_backend( + &self, + global_alloc: GlobalAlloc<'tcx>, + need_symbol_name: bool, + ) -> Result<(RValue<'gcc>, Option>), u64> { + let alloc = match global_alloc { + GlobalAlloc::Function { instance, .. } => { + return Ok(( + self.get_fn_addr(instance, None), + need_symbol_name.then(|| self.tcx.symbol_name(instance)), + )); + } + GlobalAlloc::Static(def_id) => { + assert!(self.tcx.is_static(def_id)); + return Ok(( + self.get_static(def_id).get_address(None), + need_symbol_name + .then(|| self.tcx.symbol_name(Instance::mono(self.tcx, def_id))), + )); + } + GlobalAlloc::TypeId { .. } => { + // Drop the provenance, the offset contains the bytes of the hash, so + // just return 0 as base address. + return Err(0); + } + + GlobalAlloc::Memory(alloc) => { + if alloc.inner().len() == 0 { + // For ZSTs directly codegen an aligned pointer. + // This avoids generating a zero-sized constant value and actually needing a + // real address at runtime. + return Err(alloc.inner().align.bytes()); + } + + alloc + } + + GlobalAlloc::VTable(ty, dyn_ty) => { + self.tcx + .global_alloc(self.tcx.vtable_allocation(( + ty, + dyn_ty.principal().map(|principal| { + self.tcx.instantiate_bound_regions_with_erased(principal) + }), + ))) + .unwrap_memory() + } + }; + + let value = match alloc.inner().mutability { + Mutability::Mut => { + self.static_addr_of_mut(const_alloc_to_gcc(self, alloc), alloc.inner().align, None) + } + _ => self.static_addr_of(alloc, None), + }; + if !self.sess().fewer_names() { + // FIXME(antoyo): set value name. + } + + Ok((value, None)) + } } pub fn bytes_in_context<'gcc, 'tcx>(cx: &CodegenCx<'gcc, 'tcx>, bytes: &[u8]) -> RValue<'gcc> { @@ -269,57 +332,17 @@ impl<'gcc, 'tcx> ConstCodegenMethods for CodegenCx<'gcc, 'tcx> { Scalar::Ptr(ptr, _size) => { let (prov, offset) = ptr.prov_and_relative_offset(); let alloc_id = prov.alloc_id(); - let base_addr = match self.tcx.global_alloc(alloc_id) { - GlobalAlloc::Memory(alloc) => { - // For ZSTs directly codegen an aligned pointer. - // This avoids generating a zero-sized constant value and actually needing a - // real address at runtime. - if alloc.inner().len() == 0 { - let val = alloc.inner().align.bytes().wrapping_add(offset.bytes()); - let val = self.const_usize(self.tcx.truncate_to_target_usize(val)); - return if matches!(layout.primitive(), Pointer(_)) { - self.context.new_cast(None, val, ty) - } else { - self.const_bitcast(val, ty) - }; - } - - let value = match alloc.inner().mutability { - Mutability::Mut => self.static_addr_of_mut( - const_alloc_to_gcc(self, alloc), - alloc.inner().align, - None, - ), - _ => self.static_addr_of(alloc, None), + let base_addr = match self.alloc_to_backend(self.tcx.global_alloc(alloc_id), false) + { + Ok((base_addr, _)) => base_addr, + Err(base_addr) => { + let val = base_addr.wrapping_add(offset.bytes()); + let val = self.const_usize(self.tcx.truncate_to_target_usize(val)); + return if matches!(layout.primitive(), Pointer(_)) { + self.context.new_cast(None, val, ty) + } else { + self.const_bitcast(val, ty) }; - if !self.sess().fewer_names() { - // FIXME(antoyo): set value name. - } - value - } - GlobalAlloc::Function { instance, .. } => self.get_fn_addr(instance, None), - GlobalAlloc::VTable(ty, dyn_ty) => { - let alloc = self - .tcx - .global_alloc(self.tcx.vtable_allocation(( - ty, - dyn_ty.principal().map(|principal| { - self.tcx.instantiate_bound_regions_with_erased(principal) - }), - ))) - .unwrap_memory(); - self.static_addr_of(alloc, None) - } - GlobalAlloc::TypeId { .. } => { - let val = self.const_usize(offset.bytes()); - // This is still a variable of pointer type, even though we only use the provenance - // of that pointer in CTFE and Miri. But to make LLVM's type system happy, - // we need an int-to-ptr cast here (it doesn't matter at all which provenance that picks). - return self.context.new_cast(None, val, ty); - } - GlobalAlloc::Static(def_id) => { - assert!(self.tcx.is_static(def_id)); - self.get_static(def_id).get_address(None) } }; let ptr_type = base_addr.get_type(); diff --git a/compiler/rustc_codegen_llvm/src/asm.rs b/compiler/rustc_codegen_llvm/src/asm.rs index 339347b366d88..d460d23b23d1d 100644 --- a/compiler/rustc_codegen_llvm/src/asm.rs +++ b/compiler/rustc_codegen_llvm/src/asm.rs @@ -5,7 +5,7 @@ use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_codegen_ssa::mir::operand::OperandValue; use rustc_codegen_ssa::traits::*; use rustc_data_structures::fx::FxHashMap; -use rustc_middle::mir::interpret::{GlobalAlloc, Scalar as ConstScalar}; +use rustc_middle::mir::interpret::Scalar as ConstScalar; use rustc_middle::ty::Instance; use rustc_middle::ty::layout::TyAndLayout; use rustc_middle::{bug, span_bug}; @@ -164,21 +164,10 @@ impl<'ll, 'tcx> AsmBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { let (prov, offset) = ptr.prov_and_relative_offset(); assert_eq!(offset.bytes(), 0); let global_alloc = self.tcx.global_alloc(prov.alloc_id()); - match global_alloc { - GlobalAlloc::Function { instance } => { - inputs.push(self.cx.get_fn(instance)); - op_idx.insert(idx, constraints.len()); - constraints.push("s".to_string()); - } - GlobalAlloc::Static(def_id) => { - inputs.push(self.cx.get_static(def_id)); - op_idx.insert(idx, constraints.len()); - constraints.push("s".to_string()); - } - GlobalAlloc::Memory(_) - | GlobalAlloc::VTable(..) - | GlobalAlloc::TypeId { .. } => unreachable!(), - } + let value = self.cx.alloc_to_backend(global_alloc, false, None).unwrap(); + inputs.push(value); + op_idx.insert(idx, constraints.len()); + constraints.push("s".to_string()); } }, InlineAsmOperandRef::SymThreadLocalStatic { def_id } => { @@ -236,21 +225,12 @@ impl<'ll, 'tcx> AsmBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { template_str.push_str(&string); } ConstScalar::Ptr(ptr, _) => { - let (prov, offset) = ptr.prov_and_relative_offset(); + let (_, offset) = ptr.prov_and_relative_offset(); assert_eq!(offset.bytes(), 0); - let global_alloc = self.tcx.global_alloc(prov.alloc_id()); - match global_alloc { - GlobalAlloc::Function { .. } | GlobalAlloc::Static(_) => { - // Only emit the raw symbol name - template_str.push_str(&format!( - "${{{}:c}}", - op_idx[&operand_idx] - )); - } - GlobalAlloc::Memory(_) - | GlobalAlloc::VTable(..) - | GlobalAlloc::TypeId { .. } => unreachable!(), - } + + // Only emit the raw symbol name + template_str + .push_str(&format!("${{{}:c}}", op_idx[&operand_idx])); } } } @@ -469,18 +449,8 @@ impl<'tcx> AsmCodegenMethods<'tcx> for CodegenCx<'_, 'tcx> { let (prov, offset) = ptr.prov_and_relative_offset(); assert_eq!(offset.bytes(), 0); let global_alloc = self.tcx.global_alloc(prov.alloc_id()); - let llval = match global_alloc { - GlobalAlloc::Function { instance } => self.get_fn(instance), - GlobalAlloc::Static(def_id) => self - .renamed_statics - .borrow() - .get(&def_id) - .copied() - .unwrap_or_else(|| self.get_static(def_id)), - GlobalAlloc::Memory(_) - | GlobalAlloc::VTable(..) - | GlobalAlloc::TypeId { .. } => unreachable!(), - }; + let llval = + self.alloc_to_backend(global_alloc, true, None).unwrap(); self.add_compiler_used_global(llval); let symbol = llvm::build_string(|s| unsafe { diff --git a/compiler/rustc_codegen_llvm/src/common.rs b/compiler/rustc_codegen_llvm/src/common.rs index 205e2e9a14701..1f367dc58594c 100644 --- a/compiler/rustc_codegen_llvm/src/common.rs +++ b/compiler/rustc_codegen_llvm/src/common.rs @@ -174,6 +174,79 @@ impl<'ll, CX: Borrow>> GenericCx<'ll, CX> { } } +impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> { + pub(crate) fn alloc_to_backend( + &self, + global_alloc: GlobalAlloc<'tcx>, + need_symbol_name: bool, + schema: Option<&PointerAuthSchema>, + ) -> Result<&'ll Value, u64> { + let alloc = match global_alloc { + GlobalAlloc::Function { instance, .. } => { + return Ok(self.get_fn_addr(instance, schema)); + } + GlobalAlloc::Static(def_id) => { + assert!(self.tcx.is_static(def_id)); + assert!(!self.tcx.is_thread_local_static(def_id)); + return Ok( + // `alloc_to_backend` might be called by `global_asm!` codegen. In which case + // `global_asm!` would need to find the renamed statics to use for symbol name. + self.renamed_statics + .borrow() + .get(&def_id) + .copied() + .unwrap_or_else(|| self.get_static(def_id)), + ); + } + GlobalAlloc::TypeId { .. } => { + // Drop the provenance, the offset contains the bytes of the hash, so + // just return 0 as base address. + return Err(0); + } + + GlobalAlloc::Memory(alloc) => { + if alloc.inner().len() == 0 { + // For ZSTs directly codegen an aligned pointer. + // This avoids generating a zero-sized constant value and actually needing a + // real address at runtime. + return Err(alloc.inner().align.bytes()); + } + + alloc + } + GlobalAlloc::VTable(ty, dyn_ty) => { + self.tcx + .global_alloc(self.tcx.vtable_allocation(( + ty, + dyn_ty.principal().map(|principal| { + self.tcx.instantiate_bound_regions_with_erased(principal) + }), + ))) + .unwrap_memory() + } + }; + + assert!(!need_symbol_name); + + let init = const_alloc_to_llvm(self, alloc.inner(), IsStatic::No, IsInitOrFini::No); + let alloc = alloc.inner(); + let value = match alloc.mutability { + Mutability::Mut => self.static_addr_of_mut(init, alloc.align, None), + _ => self.static_addr_of_impl(init, alloc.align, None), + }; + if !self.sess().fewer_names() && llvm::get_value_name(value).is_empty() { + let hash = self.tcx.with_stable_hashing_context(|mut hcx| { + let mut hasher = StableHasher::new(); + alloc.stable_hash(&mut hcx, &mut hasher); + hasher.finish::() + }); + llvm::set_value_name(value, format!("alloc_{hash:032x}").as_bytes()); + } + + Ok(value) + } +} + impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> { fn const_null(&self, t: &'ll Type) -> &'ll Value { unsafe { llvm::LLVMConstNull(t) } @@ -333,77 +406,19 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> { Scalar::Ptr(ptr, _size) => { let (prov, offset) = ptr.prov_and_relative_offset(); let global_alloc = self.tcx.global_alloc(prov.alloc_id()); - let base_addr = match global_alloc { - GlobalAlloc::Memory(alloc) => { - // For ZSTs directly codegen an aligned pointer. - // This avoids generating a zero-sized constant value and actually needing a - // real address at runtime. - if alloc.inner().len() == 0 { - let val = alloc.inner().align.bytes().wrapping_add(offset.bytes()); - let llval = self.const_usize(self.tcx.truncate_to_target_usize(val)); - return if matches!(layout.primitive(), Pointer(_)) { - unsafe { llvm::LLVMConstIntToPtr(llval, llty) } - } else { - self.const_bitcast(llval, llty) - }; + let base_addr_space = global_alloc.address_space(self); + let base_addr = match self.alloc_to_backend(global_alloc, false, schema) { + Ok(base_addr) => base_addr, + Err(base_addr) => { + let val = base_addr.wrapping_add(offset.bytes()); + let llval = self.const_usize(self.tcx.truncate_to_target_usize(val)); + return if matches!(layout.primitive(), Pointer(_)) { + unsafe { llvm::LLVMConstIntToPtr(llval, llty) } } else { - let init = const_alloc_to_llvm( - self, - alloc.inner(), - IsStatic::No, - IsInitOrFini::No, - ); - let alloc = alloc.inner(); - let value = match alloc.mutability { - Mutability::Mut => self.static_addr_of_mut(init, alloc.align, None), - _ => self.static_addr_of_impl(init, alloc.align, None), - }; - if !self.sess().fewer_names() && llvm::get_value_name(value).is_empty() - { - let hash = self.tcx.with_stable_hashing_context(|mut hcx| { - let mut hasher = StableHasher::new(); - alloc.stable_hash(&mut hcx, &mut hasher); - hasher.finish::() - }); - llvm::set_value_name( - value, - format!("alloc_{hash:032x}").as_bytes(), - ); - } - value - } - } - GlobalAlloc::Function { instance, .. } => self.get_fn_addr(instance, schema), - GlobalAlloc::VTable(ty, dyn_ty) => { - let alloc = self - .tcx - .global_alloc(self.tcx.vtable_allocation(( - ty, - dyn_ty.principal().map(|principal| { - self.tcx.instantiate_bound_regions_with_erased(principal) - }), - ))) - .unwrap_memory(); - let init = const_alloc_to_llvm( - self, - alloc.inner(), - IsStatic::No, - IsInitOrFini::No, - ); - self.static_addr_of_impl(init, alloc.inner().align, None) - } - GlobalAlloc::Static(def_id) => { - assert!(self.tcx.is_static(def_id)); - assert!(!self.tcx.is_thread_local_static(def_id)); - self.get_static(def_id) - } - GlobalAlloc::TypeId { .. } => { - // Drop the provenance, the offset contains the bytes of the hash - let llval = self.const_usize(offset.bytes()); - return unsafe { llvm::LLVMConstIntToPtr(llval, llty) }; + self.const_bitcast(llval, llty) + }; } }; - let base_addr_space = global_alloc.address_space(self); let llval = unsafe { llvm::LLVMConstInBoundsGEP2( self.type_i8(), From 338d560eafea3b72c517977051f1fb2672d9246a Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 18 Dec 2025 20:00:30 +0000 Subject: [PATCH 27/61] Handle pointers with offset for asm const --- .../rustc_codegen_cranelift/src/global_asm.rs | 9 ++++++-- compiler/rustc_codegen_gcc/src/asm.rs | 21 +++++++++++++----- compiler/rustc_codegen_llvm/src/asm.rs | 22 ++++++++++++++----- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/global_asm.rs b/compiler/rustc_codegen_cranelift/src/global_asm.rs index 558d3cac8e754..a64545be57efe 100644 --- a/compiler/rustc_codegen_cranelift/src/global_asm.rs +++ b/compiler/rustc_codegen_cranelift/src/global_asm.rs @@ -1,13 +1,14 @@ //! The AOT driver uses [`cranelift_object`] to write object files suitable for linking into a //! standalone executable. +use std::fmt::Write as _; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_codegen_ssa::traits::{AsmCodegenMethods, GlobalAsmOperandRef}; -use rustc_middle::mir::interpret::{GlobalAlloc, Scalar as ConstScalar}; +use rustc_middle::mir::interpret::{GlobalAlloc, PointerArithmetic, Scalar as ConstScalar}; use rustc_middle::ty::TyCtxt; use rustc_middle::ty::layout::{ FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasTyCtxt, HasTypingEnv, LayoutError, LayoutOfHelpers, @@ -129,7 +130,6 @@ fn codegen_global_asm_inner<'tcx>( } let (prov, offset) = ptr.prov_and_relative_offset(); - assert_eq!(offset.bytes(), 0); let global_alloc = tcx.global_alloc(prov.alloc_id()); let symbol = match global_alloc { GlobalAlloc::Function { instance } => { @@ -151,6 +151,11 @@ fn codegen_global_asm_inner<'tcx>( symbol.name.to_owned() }; global_asm.push_str(&escape_symbol_name(tcx, &symbol_name, span)); + + if offset != Size::ZERO { + let offset = tcx.sign_extend_to_target_isize(offset.bytes()); + write!(global_asm, "{offset:+}").unwrap(); + } } } } diff --git a/compiler/rustc_codegen_gcc/src/asm.rs b/compiler/rustc_codegen_gcc/src/asm.rs index 90576906c6702..23aeb1e06463c 100644 --- a/compiler/rustc_codegen_gcc/src/asm.rs +++ b/compiler/rustc_codegen_gcc/src/asm.rs @@ -1,8 +1,10 @@ // cSpell:ignoreRegExp [afkspqvwy]reg use std::borrow::Cow; +use std::fmt::Write; use gccjit::{LValue, RValue, ToRValue, Type}; +use rustc_abi::Size; use rustc_ast::ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_codegen_ssa::mir::operand::OperandValue; use rustc_codegen_ssa::mir::place::PlaceRef; @@ -11,7 +13,7 @@ use rustc_codegen_ssa::traits::{ GlobalAsmOperandRef, InlineAsmOperandRef, }; use rustc_middle::bug; -use rustc_middle::mir::interpret::{GlobalAlloc, Scalar}; +use rustc_middle::mir::interpret::{GlobalAlloc, PointerArithmetic, Scalar}; use rustc_middle::ty::Instance; use rustc_middle::ty::layout::LayoutOf; use rustc_span::{DUMMY_SP, Span}; @@ -405,8 +407,7 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { InlineAsmOperandRef::Const { value, ty: _ } => match value { Scalar::Int(_) => (), Scalar::Ptr(ptr, _) => { - let (prov, offset) = ptr.prov_and_relative_offset(); - assert_eq!(offset.bytes(), 0); + let (prov, _) = ptr.prov_and_relative_offset(); let global_alloc = self.tcx.global_alloc(prov.alloc_id()); let (val, sym) = self.cx.alloc_to_backend(global_alloc, true).unwrap(); const_syms.push(sym.unwrap()); @@ -509,12 +510,17 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> { Scalar::Ptr(ptr, _) => { let (_, offset) = ptr.prov_and_relative_offset(); - assert_eq!(offset.bytes(), 0); let sym = const_syms.remove(0); // FIXME(@Amanieu): Additional mangling is needed on // some targets to add a leading underscore (Mach-O) // or byte count suffixes (x86 Windows). template_str.push_str(sym.name); + + if offset != Size::ZERO { + let offset = + self.sign_extend_to_target_isize(offset.bytes()); + write!(template_str, "{offset:+}").unwrap(); + } } } } @@ -964,7 +970,6 @@ impl<'gcc, 'tcx> AsmCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { Scalar::Ptr(ptr, _) => { let (prov, offset) = ptr.prov_and_relative_offset(); - assert_eq!(offset.bytes(), 0); let global_alloc = self.tcx.global_alloc(prov.alloc_id()); let symbol_name = match global_alloc { GlobalAlloc::Function { instance } => { @@ -985,6 +990,12 @@ impl<'gcc, 'tcx> AsmCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { } }; template_str.push_str(symbol_name.name); + + if offset != Size::ZERO { + let offset = + self.sign_extend_to_target_isize(offset.bytes()); + write!(template_str, "{offset:+}").unwrap(); + } } } } diff --git a/compiler/rustc_codegen_llvm/src/asm.rs b/compiler/rustc_codegen_llvm/src/asm.rs index d460d23b23d1d..1efab9b7c496d 100644 --- a/compiler/rustc_codegen_llvm/src/asm.rs +++ b/compiler/rustc_codegen_llvm/src/asm.rs @@ -1,11 +1,12 @@ use std::assert_matches; +use std::fmt::Write; -use rustc_abi::{BackendRepr, Float, Integer, Primitive, Scalar}; +use rustc_abi::{BackendRepr, Float, Integer, Primitive, Scalar, Size}; use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_codegen_ssa::mir::operand::OperandValue; use rustc_codegen_ssa::traits::*; use rustc_data_structures::fx::FxHashMap; -use rustc_middle::mir::interpret::Scalar as ConstScalar; +use rustc_middle::mir::interpret::{PointerArithmetic, Scalar as ConstScalar}; use rustc_middle::ty::Instance; use rustc_middle::ty::layout::TyAndLayout; use rustc_middle::{bug, span_bug}; @@ -161,8 +162,7 @@ impl<'ll, 'tcx> AsmBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { InlineAsmOperandRef::Const { value, ty: _ } => match value { ConstScalar::Int(_) => (), ConstScalar::Ptr(ptr, _) => { - let (prov, offset) = ptr.prov_and_relative_offset(); - assert_eq!(offset.bytes(), 0); + let (prov, _) = ptr.prov_and_relative_offset(); let global_alloc = self.tcx.global_alloc(prov.alloc_id()); let value = self.cx.alloc_to_backend(global_alloc, false, None).unwrap(); inputs.push(value); @@ -226,11 +226,16 @@ impl<'ll, 'tcx> AsmBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { } ConstScalar::Ptr(ptr, _) => { let (_, offset) = ptr.prov_and_relative_offset(); - assert_eq!(offset.bytes(), 0); // Only emit the raw symbol name template_str .push_str(&format!("${{{}:c}}", op_idx[&operand_idx])); + + if offset != Size::ZERO { + let offset = + self.sign_extend_to_target_isize(offset.bytes()); + write!(template_str, "{offset:+}").unwrap(); + } } } } @@ -447,7 +452,6 @@ impl<'tcx> AsmCodegenMethods<'tcx> for CodegenCx<'_, 'tcx> { ConstScalar::Ptr(ptr, _) => { let (prov, offset) = ptr.prov_and_relative_offset(); - assert_eq!(offset.bytes(), 0); let global_alloc = self.tcx.global_alloc(prov.alloc_id()); let llval = self.alloc_to_backend(global_alloc, true, None).unwrap(); @@ -459,6 +463,12 @@ impl<'tcx> AsmCodegenMethods<'tcx> for CodegenCx<'_, 'tcx> { .expect("symbol is not valid UTF-8"); template_str .push_str(&escape_symbol_name(self.tcx, &symbol, span)); + + if offset != Size::ZERO { + let offset = + self.sign_extend_to_target_isize(offset.bytes()); + write!(template_str, "{offset:+}").unwrap(); + } } } } From 6deaed5275e43c87bea3021c3517979d94dab801 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 18 Dec 2025 20:45:34 +0000 Subject: [PATCH 28/61] Support codegen of asm const pointers without provenance CTFE pointers created via type ID, `without_provenance` or pointers to const ZSTs can now be codegenned with all 3 backends. These pointers are generated in the same way as integers. --- compiler/rustc_codegen_ssa/src/base.rs | 5 +++- compiler/rustc_codegen_ssa/src/common.rs | 29 +++++++++++++++++++ compiler/rustc_codegen_ssa/src/mir/block.rs | 5 +++- .../rustc_codegen_ssa/src/mir/naked_asm.rs | 6 +++- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index a409cd5002eb4..d3af6eba33374 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -426,7 +426,10 @@ where const_value ) }; - GlobalAsmOperandRef::Const { value: scalar, ty } + GlobalAsmOperandRef::Const { + value: common::asm_const_ptr_clean(cx.tcx(), scalar), + ty, + } } Err(ErrorHandled::Reported { .. }) => { // An error has already been reported and diff --git a/compiler/rustc_codegen_ssa/src/common.rs b/compiler/rustc_codegen_ssa/src/common.rs index f9b904ede9833..777f3f6b53fd4 100644 --- a/compiler/rustc_codegen_ssa/src/common.rs +++ b/compiler/rustc_codegen_ssa/src/common.rs @@ -2,6 +2,7 @@ use rustc_hir::LangItem; use rustc_hir::attrs::PeImportNameType; +use rustc_middle::mir::interpret::{GlobalAlloc, PointerArithmetic, Scalar}; use rustc_middle::ty::layout::TyAndLayout; use rustc_middle::ty::{self, Instance, ScalarInt, TyCtxt}; use rustc_middle::{bug, span_bug}; @@ -167,10 +168,38 @@ pub fn asm_const_to_str<'tcx>( ty::IntTy::I128 => (value as i128).to_string(), ty::IntTy::Isize => unreachable!(), }, + // For pointers without provenance, just print the unsigned value + ty::Ref(..) | ty::RawPtr(..) | ty::FnPtr(..) => value.to_string(), _ => span_bug!(sp, "asm const has bad type {}", ty_and_layout.ty), } } +/// "Clean" a const pointer by removing values where the resulting ASM will not be +/// ` + `. +/// +/// These values are converted to `ScalarInt`. +pub fn asm_const_ptr_clean<'tcx>(tcx: TyCtxt<'tcx>, scalar: Scalar) -> Scalar { + let Scalar::Ptr(ptr, _) = scalar else { + return scalar; + }; + let (prov, offset) = ptr.prov_and_relative_offset(); + let global_alloc = tcx.global_alloc(prov.alloc_id()); + match global_alloc { + GlobalAlloc::TypeId { .. } => { + // `TypeId` provenances are not a thing in codegen. Just erase and replace with scalar offset. + Scalar::from_u64(offset.bytes()) + } + GlobalAlloc::Memory(alloc) if alloc.inner().len() == 0 => { + // ZST const allocations don't actually get global defined when lowered. + // Turn them into integer without provenances now. + let val = alloc.inner().align.bytes().wrapping_add(offset.bytes()); + Scalar::from_target_usize(tcx.truncate_to_target_usize(val), &tcx) + } + // Other types of `GlobalAlloc` are fine. + _ => scalar, + } +} + pub fn is_mingw_gnu_toolchain(target: &Target) -> bool { target.os == Os::Windows && target.env == Env::Gnu && target.cfg_abi == CfgAbi::Unspecified } diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index e2c0cd03d85a4..8a55675bd7a38 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -1487,7 +1487,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { const_value ) }; - InlineAsmOperandRef::Const { value: scalar, ty: value.ty() } + InlineAsmOperandRef::Const { + value: common::asm_const_ptr_clean(bx.tcx(), scalar), + ty: value.ty(), + } } mir::InlineAsmOperand::SymFn { ref value } => { let const_ = self.monomorphize(value.const_); diff --git a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs index 416308c80445d..131a345fe557d 100644 --- a/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs +++ b/compiler/rustc_codegen_ssa/src/mir/naked_asm.rs @@ -11,6 +11,7 @@ use rustc_span::sym; use rustc_target::callconv::{ArgAbi, FnAbi, PassMode}; use rustc_target::spec::{Arch, BinaryFormat, Env, Os}; +use crate::common; use crate::mir::AsmCodegenMethods; use crate::traits::GlobalAsmOperandRef; @@ -85,7 +86,10 @@ fn inline_to_global_operand<'a, 'tcx, Cx: LayoutOf<'tcx, LayoutOfResult = TyAndL ) }; - GlobalAsmOperandRef::Const { value: scalar, ty: mono_type } + GlobalAsmOperandRef::Const { + value: common::asm_const_ptr_clean(cx.tcx(), scalar), + ty: mono_type, + } } InlineAsmOperand::SymFn { value } => { let mono_type = instance.instantiate_mir_and_normalize_erasing_regions( From 1fd34e44be18aa46b3ddbd3e967e74fb1e01eb2f Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 10 Jun 2026 16:53:04 +0100 Subject: [PATCH 29/61] Give codegen units symbol names that backend can use --- Cargo.lock | 1 + compiler/rustc_middle/src/mono.rs | 14 +++++++ compiler/rustc_monomorphize/Cargo.toml | 1 + .../rustc_monomorphize/src/partitioning.rs | 14 +++++++ compiler/rustc_symbol_mangling/src/lib.rs | 2 +- compiler/rustc_symbol_mangling/src/v0.rs | 37 +++++++++++++++++++ 6 files changed, 68 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 4bcfcfc8d1353..bfa0bc3d44b61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4508,6 +4508,7 @@ dependencies = [ "rustc_middle", "rustc_session", "rustc_span", + "rustc_symbol_mangling", "rustc_target", "serde", "serde_json", diff --git a/compiler/rustc_middle/src/mono.rs b/compiler/rustc_middle/src/mono.rs index 657ea939844cb..00bb3a2ee66a4 100644 --- a/compiler/rustc_middle/src/mono.rs +++ b/compiler/rustc_middle/src/mono.rs @@ -349,6 +349,11 @@ pub struct CodegenUnit<'tcx> { /// contain something unique to this crate (e.g., a module path) /// as well as the crate name and disambiguator. name: Symbol, + + /// Symbol name for this CGU. Backend may emit symbols prefixed with this name + /// and assume uniqueness. + symbol_name: Option, + items: FxIndexMap, MonoItemData>, size_estimate: usize, primary: bool, @@ -405,6 +410,7 @@ impl<'tcx> CodegenUnit<'tcx> { pub fn new(name: Symbol) -> CodegenUnit<'tcx> { CodegenUnit { name, + symbol_name: None, items: Default::default(), size_estimate: 0, primary: false, @@ -445,6 +451,14 @@ impl<'tcx> CodegenUnit<'tcx> { self.is_code_coverage_dead_code_cgu = true; } + pub fn symbol_name(&self) -> Symbol { + self.symbol_name.expect("CGU symbol name accessed before setting") + } + + pub fn set_symbol_name(&mut self, name: Symbol) { + self.symbol_name = Some(name); + } + pub fn mangle_name(human_readable_name: &str) -> BaseNString { let mut hasher = StableHasher::new(); human_readable_name.hash(&mut hasher); diff --git a/compiler/rustc_monomorphize/Cargo.toml b/compiler/rustc_monomorphize/Cargo.toml index 552c092ef7c46..58ccf77903bab 100644 --- a/compiler/rustc_monomorphize/Cargo.toml +++ b/compiler/rustc_monomorphize/Cargo.toml @@ -14,6 +14,7 @@ rustc_macros = { path = "../rustc_macros" } rustc_middle = { path = "../rustc_middle" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } +rustc_symbol_mangling = { path = "../rustc_symbol_mangling" } rustc_target = { path = "../rustc_target" } serde = "1" serde_json = "1" diff --git a/compiler/rustc_monomorphize/src/partitioning.rs b/compiler/rustc_monomorphize/src/partitioning.rs index fa09e7b49de76..5cfae525d7e5e 100644 --- a/compiler/rustc_monomorphize/src/partitioning.rs +++ b/compiler/rustc_monomorphize/src/partitioning.rs @@ -98,6 +98,7 @@ use std::fs::{self, File}; use std::io::Write; use std::path::{Path, PathBuf}; +use rustc_data_structures::either::Either; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_data_structures::sync::par_join; use rustc_data_structures::unord::{UnordMap, UnordSet}; @@ -457,6 +458,13 @@ fn merge_codegen_units<'tcx>( }; cgu.set_name(new_cgu_name); } + + // Assign symbol name to each CGU units. + cgu.set_symbol_name(Symbol::intern(&rustc_symbol_mangling::mangle_cgu( + cx.tcx, + LOCAL_CRATE, + Either::Right(cgu.name().as_str()), + ))); } // A sorted order here ensures what follows can be deterministic. @@ -491,6 +499,12 @@ fn merge_codegen_units<'tcx>( let numbered_codegen_unit_name = cgu_name_builder.build_cgu_name_no_mangle(LOCAL_CRATE, &["cgu"], Some(suffix)); cgu.set_name(numbered_codegen_unit_name); + + cgu.set_symbol_name(Symbol::intern(&rustc_symbol_mangling::mangle_cgu( + cx.tcx, + LOCAL_CRATE, + Either::Left(index.try_into().unwrap()), + ))); } } } diff --git a/compiler/rustc_symbol_mangling/src/lib.rs b/compiler/rustc_symbol_mangling/src/lib.rs index d61437212a266..ea65baf610b9a 100644 --- a/compiler/rustc_symbol_mangling/src/lib.rs +++ b/compiler/rustc_symbol_mangling/src/lib.rs @@ -103,7 +103,7 @@ mod v0; pub mod test; -pub use v0::mangle_internal_symbol; +pub use v0::{mangle_cgu, mangle_internal_symbol}; /// This function computes the symbol name for the given `instance` and the /// given instantiating crate. That is, if you know that instance X is diff --git a/compiler/rustc_symbol_mangling/src/v0.rs b/compiler/rustc_symbol_mangling/src/v0.rs index a0fe69ca2c59c..6294b3272d497 100644 --- a/compiler/rustc_symbol_mangling/src/v0.rs +++ b/compiler/rustc_symbol_mangling/src/v0.rs @@ -5,6 +5,7 @@ use std::ops::Range; use rustc_abi::{ExternAbi, Integer}; use rustc_data_structures::base_n::ToBaseN; +use rustc_data_structures::either::Either; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::intern::Interned; use rustc_data_structures::stable_hash::StableHasher; @@ -90,6 +91,42 @@ pub(super) fn mangle<'tcx>( std::mem::take(&mut p.out) } +pub fn mangle_cgu<'tcx>(tcx: TyCtxt<'tcx>, krate: CrateNum, cgu_name: Either) -> String { + let prefix = "_R"; + let mut p: V0SymbolMangler<'_> = V0SymbolMangler { + tcx, + start_offset: prefix.len(), + is_exportable: false, + paths: FxHashMap::default(), + types: FxHashMap::default(), + consts: FxHashMap::default(), + binders: vec![], + out: String::from(prefix), + }; + + match cgu_name { + Either::Left(cgu_index) => { + // If we have a CGU index, we can easily encode this with the shim mechanism. + p.path_append_ns(|p| p.print_def_path(krate.as_def_id(), &[]), 'S', cgu_index, "cgu") + .unwrap(); + } + Either::Right(name) => { + // In incremental compilation we just have a name and no index. Encode this as a str-typed generic argument to cgu shim for now. + p.out.push('I'); + p.path_append_ns(|p| p.print_def_path(krate.as_def_id(), &[]), 'S', 0, "cgu").unwrap(); + p.push("KRe"); + + for byte in name.as_bytes() { + let _ = write!(p.out, "{byte:02x}"); + } + + p.push("_E"); + } + } + + std::mem::take(&mut p.out) +} + pub fn mangle_internal_symbol<'tcx>(tcx: TyCtxt<'tcx>, item_name: &str) -> String { match item_name { // rust_eh_personality must not be renamed as LLVM hard-codes the name From c95d35e96f4a4987753f860214f365c71bef9a8e Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Fri, 19 Dec 2025 17:10:58 +0000 Subject: [PATCH 30/61] Generate unique symbol names if const pointers refer to promoted static --- compiler/rustc_codegen_gcc/src/common.rs | 15 ++++++++++++++- compiler/rustc_codegen_gcc/src/context.rs | 22 ++++++++++++++++++++++ compiler/rustc_codegen_llvm/src/common.rs | 19 +++++++++++++++++-- compiler/rustc_codegen_llvm/src/context.rs | 18 ++++++++++++++++++ 4 files changed, 71 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index d3d9f60128b5c..6bd186f1121fc 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -1,4 +1,4 @@ -use gccjit::{LValue, RValue, ToRValue, Type}; +use gccjit::{GlobalKind, LValue, RValue, ToRValue, Type}; use rustc_abi::Primitive::Pointer; use rustc_abi::{self as abi, HasDataLayout}; use rustc_codegen_ssa::traits::{ @@ -98,6 +98,19 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { } }; + if need_symbol_name { + let name = self.generate_global_symbol_name(); + + let init = crate::consts::const_alloc_to_gcc_uncached(self, alloc); + let alloc = alloc.inner(); + let typ = self.val_ty(init).get_aligned(alloc.align.bytes()); + + let global = self.declare_global_with_linkage(&name, typ, GlobalKind::Internal); + + global.global_set_initializer_rvalue(init); + return Ok((global.get_address(None), Some(SymbolName::new(self.tcx, &name)))); + } + let value = match alloc.inner().mutability { Mutability::Mut => { self.static_addr_of_mut(const_alloc_to_gcc(self, alloc), alloc.inner().align, None) diff --git a/compiler/rustc_codegen_gcc/src/context.rs b/compiler/rustc_codegen_gcc/src/context.rs index 184db4cb25778..8045e8ae9d28f 100644 --- a/compiler/rustc_codegen_gcc/src/context.rs +++ b/compiler/rustc_codegen_gcc/src/context.rs @@ -117,6 +117,9 @@ pub struct CodegenCx<'gcc, 'tcx> { /// A counter that is used for generating local symbol names local_gen_sym_counter: Cell, + /// A counter that is used for generating global symbol names + global_gen_sym_counter: Cell, + eh_personality: Cell>>, #[cfg(feature = "master")] pub rust_try_fn: Cell, Function<'gcc>)>>, @@ -296,6 +299,7 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { tcx, struct_types: Default::default(), local_gen_sym_counter: Cell::new(0), + global_gen_sym_counter: Cell::new(0), eh_personality: Cell::new(None), #[cfg(feature = "master")] rust_try_fn: Cell::new(None), @@ -599,6 +603,24 @@ impl<'b, 'tcx> CodegenCx<'b, 'tcx> { name.push_str(&(idx as u64 + ALPHANUMERIC_ONLY as u64).to_base(ALPHANUMERIC_ONLY)); name } + + /// Generates a new global symbol name with the given prefix. This symbol name must + /// only be used for definitions with `internal` or `private` linkage. + pub fn generate_global_symbol_name(&self) -> String { + let idx = self.global_gen_sym_counter.get(); + self.global_gen_sym_counter.set(idx + 1); + + let sym = self.codegen_unit.symbol_name(); + let prefix = sym.as_str(); + let mut name = String::with_capacity(prefix.len() + 6); + name.push_str(prefix); + name.push('.'); + // Offset the index by the base so that always at least two characters + // are generated. This avoids cases where the suffix is interpreted as + // size by the assembler (for m68k: .b, .w, .l). + name.push_str(&(idx as u64 + ALPHANUMERIC_ONLY as u64).to_base(ALPHANUMERIC_ONLY)); + name + } } fn to_gcc_tls_mode(tls_model: TlsModel) -> gccjit::TlsModel { diff --git a/compiler/rustc_codegen_llvm/src/common.rs b/compiler/rustc_codegen_llvm/src/common.rs index 1f367dc58594c..e900bc1aecd46 100644 --- a/compiler/rustc_codegen_llvm/src/common.rs +++ b/compiler/rustc_codegen_llvm/src/common.rs @@ -226,10 +226,25 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> { } }; - assert!(!need_symbol_name); - let init = const_alloc_to_llvm(self, alloc.inner(), IsStatic::No, IsInitOrFini::No); let alloc = alloc.inner(); + + if need_symbol_name { + // If a symbol name is needed, use `static_addr_of_mut` so we can give it unique symbol names. + let value = self.static_addr_of_mut(init, alloc.align, None); + if alloc.mutability.is_not() { + llvm::set_global_constant(value, true); + } + + // Even though we're generating with internal linkage, this symbol name still needs to + // be globally unique. LTO can rename symbol names if there are duplicates, but the + // names inserted into global asm as text cannot be updated. + let name = self.generate_global_symbol_name(); + llvm::set_value_name(value, name.as_bytes()); + llvm::set_linkage(value, llvm::Linkage::InternalLinkage); + return Ok(value); + } + let value = match alloc.mutability { Mutability::Mut => self.static_addr_of_mut(init, alloc.align, None), _ => self.static_addr_of_impl(init, alloc.align, None), diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index c018ab23c849a..8f1910eaced13 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -142,6 +142,9 @@ pub(crate) struct FullCx<'ll, 'tcx> { /// A counter that is used for generating local symbol names local_gen_sym_counter: Cell, + /// A counter that is used for generating global symbol names + global_gen_sym_counter: Cell, + /// `codegen_static` will sometimes create a second global variable with a /// different type and clear the symbol name of the original global. /// `global_asm!` needs to be able to find this new global so that it can @@ -679,6 +682,7 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> { rust_try_fn: Cell::new(None), intrinsics: Default::default(), local_gen_sym_counter: Cell::new(0), + global_gen_sym_counter: Cell::new(0), renamed_statics: Default::default(), objc_class_t: Cell::new(None), objc_classrefs: Default::default(), @@ -1122,6 +1126,20 @@ impl CodegenCx<'_, '_> { name.push_str(&(idx as u64).to_base(ALPHANUMERIC_ONLY)); name } + + /// Generates a new global symbol name with the given prefix. + pub(crate) fn generate_global_symbol_name(&self) -> String { + let idx = self.global_gen_sym_counter.get(); + self.global_gen_sym_counter.set(idx + 1); + + let sym = self.codegen_unit.symbol_name(); + let prefix = sym.as_str(); + let mut name = String::with_capacity(prefix.len() + 6); + name.push_str(prefix); + name.push('.'); + name.push_str(&(idx as u64).to_base(ALPHANUMERIC_ONLY)); + name + } } impl<'ll, CX: Borrow>> GenericCx<'ll, CX> { From aabf824843e0a5f9b1c72ec975b9bf8f03c429d3 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Wed, 22 Jul 2026 19:05:02 +0200 Subject: [PATCH 31/61] optimization: don't look for diagnostic/canonical items without rustc_attrs enabled --- compiler/rustc_passes/src/canonical_symbols.rs | 15 +++++++++------ compiler/rustc_passes/src/diagnostic_items.rs | 16 ++++++++++------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_passes/src/canonical_symbols.rs b/compiler/rustc_passes/src/canonical_symbols.rs index 746758d3e36f2..c823453ef17f4 100644 --- a/compiler/rustc_passes/src/canonical_symbols.rs +++ b/compiler/rustc_passes/src/canonical_symbols.rs @@ -1,8 +1,8 @@ use rustc_hir::{CanonicalSymbols, ForeignItemId, find_attr}; use rustc_middle::query::{LocalCrate, Providers}; use rustc_middle::ty::{Instance, List, TyCtxt}; -use rustc_span::Symbol; use rustc_span::def_id::{DefId, LOCAL_CRATE}; +use rustc_span::{Symbol, sym}; use crate::diagnostics::DuplicateCanonicalSymbolInCrate; @@ -57,12 +57,15 @@ fn canonical_symbols(tcx: TyCtxt<'_>, _: LocalCrate) -> CanonicalSymbols { // Initialize the collector. let mut canonical_symbols = CanonicalSymbols::new(); - // Collect canonical symbols in this crate. - let crate_items = tcx.hir_crate_items(()); - for id in crate_items.foreign_items() { - observe_item(tcx, &mut canonical_symbols, id); + // Optimization: can this crate even define canonical items? + // (But do not mark `rustc_attrs` as used while doing so) + if tcx.features().enabled_features().contains(&sym::rustc_attrs) { + // Collect canonical symbols in this crate. + let crate_items = tcx.hir_crate_items(()); + for id in crate_items.foreign_items() { + observe_item(tcx, &mut canonical_symbols, id); + } } - canonical_symbols } diff --git a/compiler/rustc_passes/src/diagnostic_items.rs b/compiler/rustc_passes/src/diagnostic_items.rs index 49e46a05e5ccd..e950a26af91a3 100644 --- a/compiler/rustc_passes/src/diagnostic_items.rs +++ b/compiler/rustc_passes/src/diagnostic_items.rs @@ -13,8 +13,8 @@ use rustc_hir::diagnostic_items::DiagnosticItems; use rustc_hir::{CRATE_OWNER_ID, OwnerId, find_attr}; use rustc_middle::query::{LocalCrate, Providers}; use rustc_middle::ty::TyCtxt; -use rustc_span::Symbol; use rustc_span::def_id::{DefId, LOCAL_CRATE}; +use rustc_span::{Symbol, sym}; use crate::diagnostics::DuplicateDiagnosticItemInCrate; @@ -53,15 +53,19 @@ fn report_duplicate_item( }); } -/// Traverse and collect the diagnostic items in the current +/// Traverse and collect the diagnostic items in the current crate fn diagnostic_items(tcx: TyCtxt<'_>, _: LocalCrate) -> DiagnosticItems { // Initialize the collector. let mut diagnostic_items = DiagnosticItems::default(); - // Collect diagnostic items in this crate. - let crate_items = tcx.hir_crate_items(()); - for id in crate_items.owners().chain(std::iter::once(CRATE_OWNER_ID)) { - observe_item(tcx, &mut diagnostic_items, id); + // Optimization: can this crate even define diagnostic items? + // (But do not mark `rustc_attrs` as used while doing so) + if tcx.features().enabled_features().contains(&sym::rustc_attrs) { + // Collect diagnostic items in this crate. + let crate_items = tcx.hir_crate_items(()); + for id in crate_items.owners().chain(std::iter::once(CRATE_OWNER_ID)) { + observe_item(tcx, &mut diagnostic_items, id); + } } diagnostic_items From 77977f9966b681ff865f810869859bcb2eb13a8e Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Wed, 22 Jul 2026 20:32:58 +0200 Subject: [PATCH 32/61] implement `CovariantUnsafeCell` --- compiler/rustc_hir/src/lang_items.rs | 1 + .../rustc_hir_analysis/src/variance/terms.rs | 1 + compiler/rustc_span/src/symbol.rs | 1 + library/core/src/cell.rs | 3 + .../core/src/cell/covariant_unsafe_cell.rs | 172 ++++++++++++++++++ 5 files changed, 178 insertions(+) create mode 100644 library/core/src/cell/covariant_unsafe_cell.rs diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index c7c37d30dd546..4c8c1b2c86e19 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -224,6 +224,7 @@ language_item_table! { IndexMut, sym::index_mut, index_mut_trait, Target::Trait, GenericRequirement::Exact(1); UnsafeCell, sym::unsafe_cell, unsafe_cell_type, Target::Struct, GenericRequirement::None; + CovariantUnsafeCell, sym::covariant_unsafe_cell, covariant_unsafe_cell_type, Target::Struct, GenericRequirement::Exact(1); UnsafePinned, sym::unsafe_pinned, unsafe_pinned_type, Target::Struct, GenericRequirement::None; VaArgSafe, sym::va_arg_safe, va_arg_safe, Target::Trait, GenericRequirement::None; diff --git a/compiler/rustc_hir_analysis/src/variance/terms.rs b/compiler/rustc_hir_analysis/src/variance/terms.rs index 6faeab4217e37..d22eb01467ebe 100644 --- a/compiler/rustc_hir_analysis/src/variance/terms.rs +++ b/compiler/rustc_hir_analysis/src/variance/terms.rs @@ -111,6 +111,7 @@ fn lang_items(tcx: TyCtxt<'_>) -> Vec<(LocalDefId, Vec)> { let all = [ (lang_items.phantom_data(), vec![ty::Covariant]), (lang_items.unsafe_cell_type(), vec![ty::Invariant]), + (lang_items.covariant_unsafe_cell_type(), vec![ty::Covariant]), ]; all.into_iter() // iterating over (Option, Variance) diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 3b44015b4f452..27c6bc0f4b6d7 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -737,6 +737,7 @@ symbols! { cosf64, cosf128, count, + covariant_unsafe_cell, coverage, coverage_attribute, cr, diff --git a/library/core/src/cell.rs b/library/core/src/cell.rs index 1b8ec2a91478a..7051ab22581f6 100644 --- a/library/core/src/cell.rs +++ b/library/core/src/cell.rs @@ -259,9 +259,12 @@ use crate::pin::PinCoerceUnsized; use crate::ptr::{self, NonNull}; use crate::range; +mod covariant_unsafe_cell; mod lazy; mod once; +#[unstable(feature = "covariant_unsafe_cell", issue = "159735")] +pub use covariant_unsafe_cell::CovariantUnsafeCell; #[stable(feature = "lazy_cell", since = "1.80.0")] pub use lazy::LazyCell; #[stable(feature = "once_cell", since = "1.70.0")] diff --git a/library/core/src/cell/covariant_unsafe_cell.rs b/library/core/src/cell/covariant_unsafe_cell.rs new file mode 100644 index 0000000000000..11ac9b58bbb90 --- /dev/null +++ b/library/core/src/cell/covariant_unsafe_cell.rs @@ -0,0 +1,172 @@ +use crate::cell::UnsafeCell; +use crate::fmt; +use crate::ops::CoerceUnsized; +use crate::ptr::{self, NonNull}; + +/// **Co**variant version of [`UnsafeCell`]. +#[unstable(feature = "covariant_unsafe_cell", issue = "159735")] +#[repr(transparent)] +#[rustc_pub_transparent] +// Implementation note: +// +// We could make `CovariantUnsafeCell` be the canonical lang item and make `UnsafeCell` a wrapper +// over it, with `PhantomData<*mut T>`. That would however be a huge compiler change, without clear +// benefit. +// +// As such, `CovariantUnsafeCell` is wrapping `UnsafeCell` instead. It is a lang-item only to +// hardcode its variance to be **co**variant in `T`, even though it is wrapping `UnsafeCell` which +// is **in**variant in `T`. +#[lang = "covariant_unsafe_cell"] +pub struct CovariantUnsafeCell(UnsafeCell); + +#[unstable(feature = "covariant_unsafe_cell", issue = "159735")] +impl !Sync for CovariantUnsafeCell {} + +impl CovariantUnsafeCell { + /// Constructs a new instance of `CovariantUnsafeCell` which will wrap the specified value. + /// + /// All access to the inner value through `&CovariantUnsafeCell` requires `unsafe` code. + /// + /// # Examples + /// + /// ``` + /// #![feature(covariant_unsafe_cell)] + /// use std::cell::CovariantUnsafeCell; + /// + /// let uc = CovariantUnsafeCell::new(5); + /// ``` + #[unstable(feature = "covariant_unsafe_cell", issue = "159735")] + #[rustc_const_unstable(feature = "covariant_unsafe_cell", issue = "159735")] + #[inline(always)] + pub const fn new(value: T) -> CovariantUnsafeCell { + CovariantUnsafeCell(UnsafeCell::new(value)) + } + + /// Unwraps the value, consuming the cell. + /// + /// # Examples + /// + /// ``` + /// #![feature(covariant_unsafe_cell)] + /// use std::cell::CovariantUnsafeCell; + /// + /// let uc = CovariantUnsafeCell::new(5); + /// + /// let five = uc.into_inner(); + /// ``` + #[inline(always)] + #[unstable(feature = "covariant_unsafe_cell", issue = "159735")] + #[rustc_const_unstable(feature = "covariant_unsafe_cell", issue = "159735")] + pub const fn into_inner(self) -> T { + self.0.into_inner() + } +} + +impl CovariantUnsafeCell { + /// Gets a mutable non-null pointer to the wrapped value. + /// + /// This can be cast to a pointer of any kind. When creating (shared or mutable) references, you + /// must uphold the aliasing rules; see [the `UnsafeCell` type-level docs] for more discussion + /// and caveats. + /// + /// [the `UnsafeCell` type-level docs]: super::UnsafeCell#aliasing-rules + /// + /// # Examples + /// + /// ``` + /// #![feature(covariant_unsafe_cell)] + /// use std::cell::CovariantUnsafeCell; + /// use std::ptr::NonNull; + /// + /// let uc = CovariantUnsafeCell::new(5); + /// + /// let ptr: NonNull = uc.get(); + /// ``` + #[inline(always)] + #[rustc_as_ptr] + #[rustc_should_not_be_called_on_const_items] + #[unstable(feature = "covariant_unsafe_cell", issue = "159735")] + #[rustc_const_unstable(feature = "covariant_unsafe_cell", issue = "159735")] + pub const fn get(&self) -> NonNull { + // We can just cast the pointer from `CovariantUnsafeCell` to `T` because of + // #[repr(transparent)]. + // + // Note that this is also known to be allowed for user code as per + // `#[rustc_pub_transparent]`. + // SAFETY: the pointer is not null, as it comes from a reference + unsafe { NonNull::new_unchecked(ptr::from_ref(self).cast_mut() as *mut T) } + } + + /// Returns a mutable reference to the underlying data. + /// + /// This call borrows the `CovariantUnsafeCell` mutably (at compile-time) which guarantees that + /// we possess the only reference. + /// + /// # Examples + /// + /// ``` + /// #![feature(covariant_unsafe_cell)] + /// use std::cell::CovariantUnsafeCell; + /// + /// let mut c = CovariantUnsafeCell::new(5); + /// *c.get_mut() += 1; + /// + /// assert_eq!(*c.get_mut(), 6); + /// ``` + #[inline(always)] + #[unstable(feature = "covariant_unsafe_cell", issue = "159735")] + #[rustc_const_unstable(feature = "covariant_unsafe_cell", issue = "159735")] + pub const fn get_mut(&mut self) -> &mut T { + self.0.get_mut() + } + + /// Gets a mutable pointer to the wrapped value. + /// The difference from [`get`] is that this function accepts a raw pointer, + /// which is useful to avoid the creation of temporary references. + /// + /// This can be cast to a pointer of any kind. When creating (shared or mutable) references, you + /// must uphold the aliasing rules; see [the `UnsafeCell` type-level docs] for more discussion + /// and caveats. + /// + /// [`get`]: CovariantUnsafeCell::get() + /// + /// # Examples + /// + /// Gradual initialization of an `CovariantUnsafeCell` requires `raw_get`, as + /// calling `get` would require creating a reference to uninitialized data: + /// + /// ``` + /// #![feature(covariant_unsafe_cell)] + /// use std::cell::CovariantUnsafeCell; + /// use std::mem::MaybeUninit; + /// + /// let m = MaybeUninit::>::uninit(); + /// unsafe { CovariantUnsafeCell::raw_get(m.as_ptr()).write(5); } + /// // avoid below which references to uninitialized data + /// // unsafe { CovariantUnsafeCell::get(&*m.as_ptr()).write(5); } + /// let uc = unsafe { m.assume_init() }; + /// + /// assert_eq!(uc.into_inner(), 5); + /// ``` + #[inline(always)] + #[unstable(feature = "covariant_unsafe_cell", issue = "159735")] + #[rustc_const_unstable(feature = "covariant_unsafe_cell", issue = "159735")] + pub const fn raw_get(this: *const Self) -> *mut T { + // We can just cast the pointer from `UnsafeCell` to `T` because of + // #[repr(transparent)]. + // + // Note that this is also known to be allowed for user code as per + // `#[rustc_pub_transparent]`. + this as *const T as *mut T + } +} + +#[unstable(feature = "coerce_unsized", issue = "18598")] +impl, U> CoerceUnsized> for CovariantUnsafeCell {} + +#[unstable(feature = "covariant_unsafe_cell", issue = "159735")] +impl fmt::Debug for CovariantUnsafeCell { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CovariantUnsafeCell").finish_non_exhaustive() + } +} From c37239a9d8c3272792edbbbed212cd1e64f078c6 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Wed, 22 Jul 2026 21:21:23 +0200 Subject: [PATCH 33/61] add a test checking that `CovariantUnsafeCell` is covariant --- library/core/src/cell/covariant_unsafe_cell.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/library/core/src/cell/covariant_unsafe_cell.rs b/library/core/src/cell/covariant_unsafe_cell.rs index 11ac9b58bbb90..3876cc404cd2e 100644 --- a/library/core/src/cell/covariant_unsafe_cell.rs +++ b/library/core/src/cell/covariant_unsafe_cell.rs @@ -170,3 +170,14 @@ impl fmt::Debug for CovariantUnsafeCell { f.debug_struct("CovariantUnsafeCell").finish_non_exhaustive() } } + +#[cfg(test)] +mod tests { + use super::*; + + fn _covarience<'short, 'long: 'short>( + x: CovariantUnsafeCell<&'long ()>, + ) -> CovariantUnsafeCell<&'short ()> { + x + } +} From e3712ee699b86af81c1d610ed2cd01dd16a2ffba Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 22 Jul 2026 22:20:46 +0200 Subject: [PATCH 34/61] x test cargo-miri: run all subcrate tests as well also add a test for cross-crate static initializers --- src/bootstrap/src/core/build_steps/test.rs | 4 ++ .../exported-symbol-dep/src/lib.rs | 46 +++++++++++++++++++ .../exported-symbol/src/lib.rs | 1 + src/tools/miri/test-cargo-miri/src/main.rs | 12 +++-- 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 9ebe3191db40f..765a51c3901a0 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -839,6 +839,10 @@ impl Step for CargoMiri { SourceType::Submodule, &[], ); + // Run subcrate tests as well. + cargo.arg("--workspace"); + // Some tests need isolation disabled. + cargo.env("MIRIFLAGS", "-Zmiri-disable-isolation"); // If we are testing stage 2+ cargo miri, make sure that it works with the in-tree cargo. // We want to do this *somewhere* to ensure that Miri + nightly cargo actually works. diff --git a/src/tools/miri/test-cargo-miri/exported-symbol-dep/src/lib.rs b/src/tools/miri/test-cargo-miri/exported-symbol-dep/src/lib.rs index 5b8a314ae7324..cd2a1e902fc2b 100644 --- a/src/tools/miri/test-cargo-miri/exported-symbol-dep/src/lib.rs +++ b/src/tools/miri/test-cargo-miri/exported-symbol-dep/src/lib.rs @@ -11,3 +11,49 @@ impl AssocFn { -123456 } } + +// Also check static constructors in dependencies are run. + +#[rustfmt::skip] +#[macro_export] +macro_rules! ctor { + ($ident:ident = $ctor:ident) => { + #[cfg_attr( + all(any( + target_os = "linux", + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "solaris", + target_os = "none", + target_family = "wasm", + )), + link_section = ".init_array" + )] + #[cfg_attr(windows, link_section = ".CRT$XCU")] + #[cfg_attr( + any(target_os = "macos", target_os = "ios"), + // We do not set the `mod_init_funcs` flag here since ctor/inventory also do not do + // that. See . + link_section = "__DATA,__mod_init_func" + )] + #[used] + static $ident: unsafe extern "C" fn() = $ctor; + }; +} + +static mut INITIALIZED: bool = false; + +unsafe extern "C" fn ctor() { + unsafe { INITIALIZED = true }; +} + +pub fn check_initialized() { + assert!(unsafe { INITIALIZED }); +} + +ctor! { CTOR = ctor } diff --git a/src/tools/miri/test-cargo-miri/exported-symbol/src/lib.rs b/src/tools/miri/test-cargo-miri/exported-symbol/src/lib.rs index de55eb2a1a5a0..ceb8a3633c5e1 100644 --- a/src/tools/miri/test-cargo-miri/exported-symbol/src/lib.rs +++ b/src/tools/miri/test-cargo-miri/exported-symbol/src/lib.rs @@ -1 +1,2 @@ extern crate exported_symbol_dep; +pub use exported_symbol_dep::check_initialized; diff --git a/src/tools/miri/test-cargo-miri/src/main.rs b/src/tools/miri/test-cargo-miri/src/main.rs index 00a239a9161a1..1568da43afe30 100644 --- a/src/tools/miri/test-cargo-miri/src/main.rs +++ b/src/tools/miri/test-cargo-miri/src/main.rs @@ -67,6 +67,10 @@ fn main() { mod test { use byteorder_2::{BigEndian, ByteOrder}; + extern crate cargo_miri_test; + extern crate exported_symbol; + extern crate issue_rust_86261; + // Make sure in-crate tests with dev-dependencies work #[test] fn dev_dependency() { @@ -75,9 +79,6 @@ mod test { #[test] fn exported_symbol() { - extern crate cargo_miri_test; - extern crate exported_symbol; - extern crate issue_rust_86261; // Test calling exported symbols in (transitive) dependencies. // Repeat calls to make sure the `Instance` cache is not broken. for _ in 0..3 { @@ -95,4 +96,9 @@ mod test { unsafe { no_mangle_generic() } } } + + #[test] + fn static_initializer_in_dep() { + exported_symbol::check_initialized(); + } } From b7973ab76afb60c2c2f81e8cc2e4dc8176b7c077 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 22 Jul 2026 18:47:27 +0200 Subject: [PATCH 35/61] clarify some potentially-confusing parts of the UnsafeCell docs --- library/core/src/cell.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/library/core/src/cell.rs b/library/core/src/cell.rs index 1b8ec2a91478a..7f2917e36d7a5 100644 --- a/library/core/src/cell.rs +++ b/library/core/src/cell.rs @@ -2150,8 +2150,9 @@ impl fmt::Display for RefMut<'_, T> { /// use `UnsafeCell` to wrap their data. /// /// Note that only the immutability guarantee for shared references is affected by `UnsafeCell`. The -/// uniqueness guarantee for mutable references is unaffected. There is *no* legal way to obtain -/// aliasing `&mut`, not even with `UnsafeCell`. +/// uniqueness guarantee for mutable references is unaffected. As explained below, for the duration +/// of the lifetime of an `&mut`, no other reference may exist and no pointer may be used to access +/// that memory; this applies even with `UnsafeCell`. /// /// `UnsafeCell` does nothing to avoid data races; they are still undefined behavior. If multiple /// threads have access to the same `UnsafeCell`, they must follow the usual rules of the @@ -2171,11 +2172,13 @@ impl fmt::Display for RefMut<'_, T> { /// /// - If you create a safe reference with lifetime `'a` (either a `&T` or `&mut T` reference), then /// you must not access the data in any way that contradicts that reference for the remainder of -/// `'a`. For example, this means that if you take the `*mut T` from an `UnsafeCell` and cast it -/// to a `&T`, then the data in `T` must remain immutable (modulo any `UnsafeCell` data found -/// within `T`, of course) until that reference's lifetime expires. Similarly, if you create a -/// `&mut T` reference, then you must not access the data within the -/// `UnsafeCell` until that reference expires. +/// `'a`, and you must not create any contradicting references. For example, this means that if +/// you take the `*mut T` from an `UnsafeCell` and cast it to a `&T`, then the data in `T` must +/// remain immutable (modulo any `UnsafeCell` data found within `T`, of course) until that +/// reference's lifetime expires, and no `&mut` reference to this data may be created. Similarly, +/// if you create a `&mut T` reference, then you must not access the data within the `UnsafeCell` +/// with any other pointer/reference until that reference expires, and no reference of any kind +/// may be created. /// /// - For both `&T` without `UnsafeCell<_>` and `&mut T`, you must also not deallocate the data /// until the reference expires. As a special exception, given a `&T`, any part of it that is @@ -2200,7 +2203,7 @@ impl fmt::Display for RefMut<'_, T> { /// Note that whilst mutating the contents of a `&UnsafeCell` (even while other /// `&UnsafeCell` references alias the cell) is /// ok (provided you enforce the above invariants some other way), it is still undefined behavior -/// to have multiple `&mut UnsafeCell` aliases. That is, `UnsafeCell` is a wrapper +/// to have aliasing `&mut UnsafeCell` (or aliasing `&mut` of *any* type). That is, `UnsafeCell` is a wrapper /// designed to have a special interaction with _shared_ accesses (_i.e._, through an /// `&UnsafeCell<_>` reference); there is no magic whatsoever when dealing with _exclusive_ /// accesses (_e.g._, through a `&mut UnsafeCell<_>`): neither the cell nor the wrapped value From 678de5f12f12ce9312a1b69430cff055e865321f Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 22 Jul 2026 18:54:23 +0200 Subject: [PATCH 36/61] allow accessing the contents of UnsafeCell without going through get / raw_get --- library/core/src/cell.rs | 39 +++++++++++++-------------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/library/core/src/cell.rs b/library/core/src/cell.rs index 7f2917e36d7a5..e6a7acd74d9d9 100644 --- a/library/core/src/cell.rs +++ b/library/core/src/cell.rs @@ -2226,36 +2226,20 @@ impl fmt::Display for RefMut<'_, T> { /// order to avoid its interior mutability property from spreading from `T` into the `Outer` type, /// thus this can cause distortions in the type size in these cases. /// -/// Note that the only valid way to obtain a `*mut T` pointer to the contents of a -/// _shared_ `UnsafeCell` is through [`.get()`] or [`.raw_get()`]. A `&T` or `&mut T` reference -/// can then be obtained from that pointer, as long as the aliasing rules outlined above are obeyed. -/// Even though `T` and `UnsafeCell` have the -/// same memory layout, the following is not allowed and undefined behavior: +/// The following examples make use of this guarantee: /// -/// ```rust,compile_fail +/// ```rust /// # use std::cell::UnsafeCell; -/// unsafe fn not_allowed(ptr: &UnsafeCell) -> &mut T { +/// /// # Safety +/// /// The caller must not call `get_mut_unchecked` again (on any alias of `ptr`) for the duration +/// /// of the lifetime of the returned reference. +/// # #[allow(invalid_reference_casting)] // FIXME should the lint really fire here? +/// unsafe fn get_mut_unchecked(ptr: &UnsafeCell) -> &mut T { /// let t = ptr as *const UnsafeCell as *mut T; -/// // This is undefined behavior, because the `*mut T` pointer -/// // was not obtained through `.get()` nor `.raw_get()`: /// unsafe { &mut *t } /// } /// ``` /// -/// Instead, do this: -/// -/// ```rust -/// # use std::cell::UnsafeCell; -/// // Safety: the caller must ensure that there are no references that -/// // point to the *contents* of the `UnsafeCell`. -/// unsafe fn get_mut(ptr: &UnsafeCell) -> &mut T { -/// unsafe { &mut *ptr.get() } -/// } -/// ``` -/// -/// Converting in the other direction from a `&mut T` -/// to an `&UnsafeCell` is allowed: -/// /// ```rust /// # use std::cell::UnsafeCell; /// fn get_shared(ptr: &mut T) -> &UnsafeCell { @@ -2266,7 +2250,6 @@ impl fmt::Display for RefMut<'_, T> { /// ``` /// /// [niche]: https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#niche -/// [`.raw_get()`]: `UnsafeCell::raw_get` /// /// # Examples /// @@ -2428,6 +2411,9 @@ impl UnsafeCell { /// must uphold the aliasing rules; see [the type-level docs][UnsafeCell#aliasing-rules] for /// more discussion and caveats. /// + /// This is equivalent to casting `self` to a raw pointer and then casting that raw + /// pointer to `*mut T`. + /// /// # Examples /// /// ``` @@ -2445,8 +2431,7 @@ impl UnsafeCell { #[rustc_should_not_be_called_on_const_items] pub const fn get(&self) -> *mut T { // We can just cast the pointer from `UnsafeCell` to `T` because of - // #[repr(transparent)]. This exploits std's special status, there is - // no guarantee for user code that this will work in future versions of the compiler! + // #[repr(transparent)]. self as *const UnsafeCell as *const T as *mut T } @@ -2480,6 +2465,8 @@ impl UnsafeCell { /// must uphold the aliasing rules; see [the type-level docs][UnsafeCell#aliasing-rules] for /// more discussion and caveats. /// + /// This is equivalent to casting `this` to `*mut T`. + /// /// [`get`]: UnsafeCell::get() /// /// # Examples From 8ed6ffff4e41470b7b9ff99a8baa43805791ca1c Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 11 Jul 2026 14:45:53 +0000 Subject: [PATCH 37/61] Previously both free functions and associated functions creates a FnDef const after scheduling. That works only when a later phase emits a real diagnostics. For function item (associated fn), the lowering needs parent trait and self args --- .../rustc_hir_analysis/src/hir_ty_lowering/mod.rs | 13 ++++++++++++- compiler/rustc_trait_selection/src/traits/wf.rs | 9 +++++++-- tests/crashes/138088.rs | 5 ----- .../mgca/trait-assoc-fn-as-const-arg.rs | 10 ++++++++++ .../mgca/trait-assoc-fn-as-const-arg.stderr | 10 ++++++++++ 5 files changed, 39 insertions(+), 8 deletions(-) delete mode 100644 tests/crashes/138088.rs create mode 100644 tests/ui/const-generics/mgca/trait-assoc-fn-as-const-arg.rs create mode 100644 tests/ui/const-generics/mgca/trait-assoc-fn-as-const-arg.stderr diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index b59bed76eadd7..fa0f57a2bf6d5 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -2935,8 +2935,19 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { Res::Def(DefKind::Static { .. }, _) => { span_bug!(span, "use of bare `static` ConstArgKind::Path's not yet supported") } + Res::Def(DefKind::AssocFn, def_id) => { + self.dcx().span_delayed_bug(span, "function items cannot be used as const args"); + let parent_args = ty::GenericArgs::identity_for_item(tcx, tcx.parent(def_id)); + let args = self.lower_generic_args_of_assoc_item( + span, + def_id, + path.segments.last().unwrap(), + parent_args, + ); + Const::zero_sized(tcx, Ty::new_fn_def(tcx, def_id, args)) + } // FIXME(const_generics): create real const to allow fn items as const paths - Res::Def(DefKind::Fn | DefKind::AssocFn, did) => { + Res::Def(DefKind::Fn, did) => { self.dcx().span_delayed_bug(span, "function items cannot be used as const args"); let args = self.lower_generic_args_of_path_segment( span, diff --git a/compiler/rustc_trait_selection/src/traits/wf.rs b/compiler/rustc_trait_selection/src/traits/wf.rs index 95043385cf17a..e4fccabcfc185 100644 --- a/compiler/rustc_trait_selection/src/traits/wf.rs +++ b/compiler/rustc_trait_selection/src/traits/wf.rs @@ -830,8 +830,13 @@ impl<'a, 'tcx> TypeVisitor> for WfPredicates<'a, 'tcx> { // perfect and there may be ways to abuse the fact that we // ignore requirements with escaping bound vars. That's a // more general issue however. - let fn_sig = tcx.fn_sig(did).instantiate(tcx, args).skip_norm_wip(); - fn_sig.output().skip_binder().visit_with(self); + // If this is the item whose signature is currently being checked, + // its output is already on the WF stack. Walking it again can + // recurse through recovery consts that mention the same fn item. + if did.as_local() != Some(self.body_def_id) { + let fn_sig = tcx.fn_sig(did).instantiate(tcx, args).skip_norm_wip(); + fn_sig.output().skip_binder().visit_with(self); + } let obligations = self.nominal_obligations(did, args); self.out.extend(obligations); diff --git a/tests/crashes/138088.rs b/tests/crashes/138088.rs deleted file mode 100644 index bd0d0949d43d2..0000000000000 --- a/tests/crashes/138088.rs +++ /dev/null @@ -1,5 +0,0 @@ -//@ known-bug: #138088 -#![feature(min_generic_const_args)] -trait Bar { - fn x(&self) -> [i32; core::direct_const_arg!(Bar::x)]; -} diff --git a/tests/ui/const-generics/mgca/trait-assoc-fn-as-const-arg.rs b/tests/ui/const-generics/mgca/trait-assoc-fn-as-const-arg.rs new file mode 100644 index 0000000000000..7234650b39c41 --- /dev/null +++ b/tests/ui/const-generics/mgca/trait-assoc-fn-as-const-arg.rs @@ -0,0 +1,10 @@ +#![feature(min_generic_const_args)] + +// Regression test for #138088. assoc functions cannot be lowered as const +// args, and this should emit a regular diagnostic instead of ICEing. +trait Bar { + fn x(&self) -> [i32; Bar::x]; + //~^ ERROR the constant `::x` is not of type `usize` +} + +fn main() {} diff --git a/tests/ui/const-generics/mgca/trait-assoc-fn-as-const-arg.stderr b/tests/ui/const-generics/mgca/trait-assoc-fn-as-const-arg.stderr new file mode 100644 index 0000000000000..9b7c42bee293c --- /dev/null +++ b/tests/ui/const-generics/mgca/trait-assoc-fn-as-const-arg.stderr @@ -0,0 +1,10 @@ +error: the constant `::x` is not of type `usize` + --> $DIR/trait-assoc-fn-as-const-arg.rs:6:20 + | +LL | fn x(&self) -> [i32; Bar::x]; + | ^^^^^^^^^^^^^ expected `usize`, found fn item + | + = note: the length of array `[i32; ::x]` must be type `usize` + +error: aborting due to 1 previous error + From ff7f240a75e9c4073ae2d48efabafa74ce999578 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 23 Jul 2026 14:40:08 +0000 Subject: [PATCH 38/61] emit error for both AssocFn and Fn const lowering and add fixme --- .../src/hir_ty_lowering/mod.rs | 56 ++++--------------- .../rustc_trait_selection/src/traits/wf.rs | 9 +-- 2 files changed, 12 insertions(+), 53 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs index fa0f57a2bf6d5..0334b767d2dbd 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs @@ -2935,53 +2935,17 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { Res::Def(DefKind::Static { .. }, _) => { span_bug!(span, "use of bare `static` ConstArgKind::Path's not yet supported") } - Res::Def(DefKind::AssocFn, def_id) => { - self.dcx().span_delayed_bug(span, "function items cannot be used as const args"); - let parent_args = ty::GenericArgs::identity_for_item(tcx, tcx.parent(def_id)); - let args = self.lower_generic_args_of_assoc_item( - span, - def_id, - path.segments.last().unwrap(), - parent_args, - ); - Const::zero_sized(tcx, Ty::new_fn_def(tcx, def_id, args)) - } - // FIXME(const_generics): create real const to allow fn items as const paths - Res::Def(DefKind::Fn, did) => { - self.dcx().span_delayed_bug(span, "function items cannot be used as const args"); - let args = self.lower_generic_args_of_path_segment( - span, - did, - path.segments.last().unwrap(), - ); - - if self.tcx().generics_of(did).own_synthetic_params_count() == 0 { - // FIXME(156581): actually instantiate the binder correctly (turbofishing/fndef changes) - ty::Const::zero_sized(tcx, Ty::new_fn_def(tcx, did, ty::Binder::dummy(args))) - } else { - let tcx = self.tcx(); - let generics = tcx.generics_of(did); - - // Use infer tys for synthetic params; otherwise the impl header's trait ref may - // contain callee-owned synthetic params and fail when instantiated with impl args. - // See issue #155834 - let args = args.iter().enumerate().map(|(index, arg)| { - let param = generics.param_at(index, tcx); - if param.kind.is_synthetic() { - self.ty_infer(Some(param), span).into() - } else { - arg - } - }); - - // FIXME(156581): actually instantiate the binder correctly (turbofishing/fndef changes) - ty::Const::zero_sized( - tcx, - Ty::new_fn_def(tcx, did, ty::Binder::dummy(args.collect::>())), - ) - } + // FIXME(const_generics): create real consts to allow fn items as const paths. + // Lowering these to recovered `FnDef` consts currently interacts poorly with WF + // checking: WF of a `FnDef` walks the function signature, so a signature that mentions + // the same function item as a const arg can recurse until it overflows/segfaults. + Res::Def(DefKind::Fn | DefKind::AssocFn, _) => { + let guar = self + .dcx() + .struct_span_err(span, "function items cannot be used as const args") + .emit(); + Const::new_error(tcx, guar) } - // Exhaustive match to be clear about what exactly we're considering to be // an invalid Res for a const path. res @ (Res::Def( diff --git a/compiler/rustc_trait_selection/src/traits/wf.rs b/compiler/rustc_trait_selection/src/traits/wf.rs index e4fccabcfc185..95043385cf17a 100644 --- a/compiler/rustc_trait_selection/src/traits/wf.rs +++ b/compiler/rustc_trait_selection/src/traits/wf.rs @@ -830,13 +830,8 @@ impl<'a, 'tcx> TypeVisitor> for WfPredicates<'a, 'tcx> { // perfect and there may be ways to abuse the fact that we // ignore requirements with escaping bound vars. That's a // more general issue however. - // If this is the item whose signature is currently being checked, - // its output is already on the WF stack. Walking it again can - // recurse through recovery consts that mention the same fn item. - if did.as_local() != Some(self.body_def_id) { - let fn_sig = tcx.fn_sig(did).instantiate(tcx, args).skip_norm_wip(); - fn_sig.output().skip_binder().visit_with(self); - } + let fn_sig = tcx.fn_sig(did).instantiate(tcx, args).skip_norm_wip(); + fn_sig.output().skip_binder().visit_with(self); let obligations = self.nominal_obligations(did, args); self.out.extend(obligations); From 421d5f5e35e2d88825833b0b4550ef1d6ff3f0c2 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 23 Jul 2026 14:40:25 +0000 Subject: [PATCH 39/61] bless the fallout --- .../fn-item-as-const-arg-137084.rs | 3 +- .../fn-item-as-const-arg-137084.stderr | 33 ++----------------- .../ice-151186-fn-ptr-in-where-clause.rs | 2 +- .../ice-151186-fn-ptr-in-where-clause.stderr | 18 ++++------ .../mgca/bad-impl-trait-with-apit.rs | 2 +- .../mgca/bad-impl-trait-with-apit.stderr | 5 ++- .../mgca/missing_generic_params.rs | 2 +- .../mgca/missing_generic_params.stderr | 15 ++------- .../mgca/size-of-generic-ptr-in-array-len.rs | 3 +- .../size-of-generic-ptr-in-array-len.stderr | 12 +++++-- .../mgca/synth-gen-arg-ice-158152.rs | 2 +- .../mgca/synth-gen-arg-ice-158152.stderr | 7 ++-- .../mgca/trait-assoc-fn-as-const-arg.rs | 5 ++- .../mgca/trait-assoc-fn-as-const-arg.stderr | 17 +++++++--- .../mgca/tuple_ctor_erroneous.rs | 3 +- .../mgca/tuple_ctor_erroneous.stderr | 16 ++++++--- .../mgca/unexpected-fn-item-in-array.rs | 2 +- .../mgca/unexpected-fn-item-in-array.stderr | 8 ++--- 18 files changed, 64 insertions(+), 91 deletions(-) diff --git a/tests/ui/const-generics/fn-item-as-const-arg-137084.rs b/tests/ui/const-generics/fn-item-as-const-arg-137084.rs index c8679494955b9..0d8d2effc3da0 100644 --- a/tests/ui/const-generics/fn-item-as-const-arg-137084.rs +++ b/tests/ui/const-generics/fn-item-as-const-arg-137084.rs @@ -7,8 +7,7 @@ fn a() {} fn d(e: &String) { a:: - //~^ ERROR mismatched types - //~| ERROR the constant `d` is not of type `i32` + //~^ ERROR function items cannot be used as const args } fn main() {} diff --git a/tests/ui/const-generics/fn-item-as-const-arg-137084.stderr b/tests/ui/const-generics/fn-item-as-const-arg-137084.stderr index 58b74c655cd23..ee1b54889e0bb 100644 --- a/tests/ui/const-generics/fn-item-as-const-arg-137084.stderr +++ b/tests/ui/const-generics/fn-item-as-const-arg-137084.stderr @@ -1,35 +1,8 @@ -error[E0308]: mismatched types - --> $DIR/fn-item-as-const-arg-137084.rs:9:5 - | -LL | fn a() {} - | -------------------- function `a` defined here -LL | fn d(e: &String) { -LL | a:: - | ^^^^^^ expected `()`, found fn item - | - = note: expected unit type `()` - found fn item `fn() {a::}` -help: try adding a return type - | -LL | fn d(e: &String) -> fn() { - | +++++++ -help: use parentheses to call this function - | -LL | a::() - | ++ - -error: the constant `d` is not of type `i32` +error: function items cannot be used as const args --> $DIR/fn-item-as-const-arg-137084.rs:9:9 | LL | a:: - | ^ expected `i32`, found fn item - | -note: required by a const generic parameter in `a` - --> $DIR/fn-item-as-const-arg-137084.rs:7:6 - | -LL | fn a() {} - | ^^^^^^^^^^^^ required by this const generic parameter in `a` + | ^ -error: aborting due to 2 previous errors +error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/const-generics/ice-151186-fn-ptr-in-where-clause.rs b/tests/ui/const-generics/ice-151186-fn-ptr-in-where-clause.rs index d1c5db5ae0345..fb9831d493465 100644 --- a/tests/ui/const-generics/ice-151186-fn-ptr-in-where-clause.rs +++ b/tests/ui/const-generics/ice-151186-fn-ptr-in-where-clause.rs @@ -11,6 +11,6 @@ trait MyTrait ()> {} fn foo<'a>(x: &'a ()) -> &'a () { x } impl Maybe for T where T: MyTrait<{ foo }> {} -//~^ ERROR the constant `foo` is not of type `fn()` +//~^ ERROR function items cannot be used as const args fn main() {} diff --git a/tests/ui/const-generics/ice-151186-fn-ptr-in-where-clause.stderr b/tests/ui/const-generics/ice-151186-fn-ptr-in-where-clause.stderr index 7c9d7ee7dde59..3de24c8141aa2 100644 --- a/tests/ui/const-generics/ice-151186-fn-ptr-in-where-clause.stderr +++ b/tests/ui/const-generics/ice-151186-fn-ptr-in-where-clause.stderr @@ -1,3 +1,9 @@ +error: function items cannot be used as const args + --> $DIR/ice-151186-fn-ptr-in-where-clause.rs:13:43 + | +LL | impl Maybe for T where T: MyTrait<{ foo }> {} + | ^^^ + error: using function pointers as const generic parameters is forbidden --> $DIR/ice-151186-fn-ptr-in-where-clause.rs:8:24 | @@ -6,17 +12,5 @@ LL | trait MyTrait ()> {} | = note: the only supported types are integers, `bool`, and `char` -error: the constant `foo` is not of type `fn()` - --> $DIR/ice-151186-fn-ptr-in-where-clause.rs:13:33 - | -LL | impl Maybe for T where T: MyTrait<{ foo }> {} - | ^^^^^^^^^^^^^^^^ expected fn pointer, found fn item - | -note: required by a const generic parameter in `MyTrait` - --> $DIR/ice-151186-fn-ptr-in-where-clause.rs:8:15 - | -LL | trait MyTrait ()> {} - | ^^^^^^^^^^^^^^^^^^^ required by this const generic parameter in `MyTrait` - error: aborting due to 2 previous errors diff --git a/tests/ui/const-generics/mgca/bad-impl-trait-with-apit.rs b/tests/ui/const-generics/mgca/bad-impl-trait-with-apit.rs index a049b05e64363..1ecf303dbb695 100644 --- a/tests/ui/const-generics/mgca/bad-impl-trait-with-apit.rs +++ b/tests/ui/const-generics/mgca/bad-impl-trait-with-apit.rs @@ -6,7 +6,7 @@ trait Trait {} impl<'t> Trait for [(); N] {} -//~^ ERROR the placeholder `_` is not allowed within types on item signatures for implementations +//~^ ERROR function items cannot be used as const args fn N(arg: impl Trait) {} diff --git a/tests/ui/const-generics/mgca/bad-impl-trait-with-apit.stderr b/tests/ui/const-generics/mgca/bad-impl-trait-with-apit.stderr index a5caa077c7620..2f87d47fd5c5c 100644 --- a/tests/ui/const-generics/mgca/bad-impl-trait-with-apit.stderr +++ b/tests/ui/const-generics/mgca/bad-impl-trait-with-apit.stderr @@ -1,9 +1,8 @@ -error[E0121]: the placeholder `_` is not allowed within types on item signatures for implementations +error: function items cannot be used as const args --> $DIR/bad-impl-trait-with-apit.rs:8:25 | LL | impl<'t> Trait for [(); N] {} - | ^ not allowed in type signatures + | ^ error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0121`. diff --git a/tests/ui/const-generics/mgca/missing_generic_params.rs b/tests/ui/const-generics/mgca/missing_generic_params.rs index f5ad80011435b..2c28b0f038e6a 100644 --- a/tests/ui/const-generics/mgca/missing_generic_params.rs +++ b/tests/ui/const-generics/mgca/missing_generic_params.rs @@ -11,6 +11,6 @@ #![allow(incomplete_features)] trait Trait {} impl Trait for [(); N] {} -//~^ ERROR: missing generics for function `N` +//~^ ERROR function items cannot be used as const args fn N() {} pub fn main() {} diff --git a/tests/ui/const-generics/mgca/missing_generic_params.stderr b/tests/ui/const-generics/mgca/missing_generic_params.stderr index 78010c756212c..60b758ca07b97 100644 --- a/tests/ui/const-generics/mgca/missing_generic_params.stderr +++ b/tests/ui/const-generics/mgca/missing_generic_params.stderr @@ -1,19 +1,8 @@ -error[E0107]: missing generics for function `N` +error: function items cannot be used as const args --> $DIR/missing_generic_params.rs:13:21 | LL | impl Trait for [(); N] {} - | ^ expected 1 generic argument - | -note: function defined here, with 1 generic parameter: `T` - --> $DIR/missing_generic_params.rs:15:4 - | -LL | fn N() {} - | ^ - -help: add missing generic argument - | -LL | impl Trait for [(); N] {} - | +++ + | ^ error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0107`. diff --git a/tests/ui/const-generics/mgca/size-of-generic-ptr-in-array-len.rs b/tests/ui/const-generics/mgca/size-of-generic-ptr-in-array-len.rs index 919b3bce9f260..706b880680fe2 100644 --- a/tests/ui/const-generics/mgca/size-of-generic-ptr-in-array-len.rs +++ b/tests/ui/const-generics/mgca/size-of-generic-ptr-in-array-len.rs @@ -4,7 +4,8 @@ fn foo() { [0; size_of::<*mut T>()]; - //~^ ERROR: complex const arguments must be placed inside of a `const` block + //~^ ERROR function items cannot be used as const args + //~| ERROR tuple constructor with invalid base path [0; const { size_of::<*mut T>() }]; //~^ ERROR: generic parameters may not be used in const operations [0; const { size_of::<*mut i32>() }]; diff --git a/tests/ui/const-generics/mgca/size-of-generic-ptr-in-array-len.stderr b/tests/ui/const-generics/mgca/size-of-generic-ptr-in-array-len.stderr index 2752484046341..e28cde6f63342 100644 --- a/tests/ui/const-generics/mgca/size-of-generic-ptr-in-array-len.stderr +++ b/tests/ui/const-generics/mgca/size-of-generic-ptr-in-array-len.stderr @@ -1,16 +1,22 @@ -error: complex const arguments must be placed inside of a `const` block +error: function items cannot be used as const args + --> $DIR/size-of-generic-ptr-in-array-len.rs:6:9 + | +LL | [0; size_of::<*mut T>()]; + | ^^^^^^^^^^^^^^^^^ + +error: tuple constructor with invalid base path --> $DIR/size-of-generic-ptr-in-array-len.rs:6:9 | LL | [0; size_of::<*mut T>()]; | ^^^^^^^^^^^^^^^^^^^ error: generic parameters may not be used in const operations - --> $DIR/size-of-generic-ptr-in-array-len.rs:8:32 + --> $DIR/size-of-generic-ptr-in-array-len.rs:9:32 | LL | [0; const { size_of::<*mut T>() }]; | ^ | = help: add `#![feature(generic_const_args)]` and extract the expression into a `type const` item -error: aborting due to 2 previous errors +error: aborting due to 3 previous errors diff --git a/tests/ui/const-generics/mgca/synth-gen-arg-ice-158152.rs b/tests/ui/const-generics/mgca/synth-gen-arg-ice-158152.rs index 1f3519efe3272..62be08195bb2c 100644 --- a/tests/ui/const-generics/mgca/synth-gen-arg-ice-158152.rs +++ b/tests/ui/const-generics/mgca/synth-gen-arg-ice-158152.rs @@ -4,7 +4,7 @@ trait A {} trait Trait {} impl A<[usize; fn_item]> for () {} -//~^ ERROR: the placeholder `_` is not allowed within types on item signatures for implementations +//~^ ERROR function items cannot be used as const args fn fn_item(_: impl Trait) {} //~^ ERROR: type provided when a constant was expected diff --git a/tests/ui/const-generics/mgca/synth-gen-arg-ice-158152.stderr b/tests/ui/const-generics/mgca/synth-gen-arg-ice-158152.stderr index a68c0a1cb409d..d615fad3b1edf 100644 --- a/tests/ui/const-generics/mgca/synth-gen-arg-ice-158152.stderr +++ b/tests/ui/const-generics/mgca/synth-gen-arg-ice-158152.stderr @@ -1,8 +1,8 @@ -error[E0121]: the placeholder `_` is not allowed within types on item signatures for implementations +error: function items cannot be used as const args --> $DIR/synth-gen-arg-ice-158152.rs:6:16 | LL | impl A<[usize; fn_item]> for () {} - | ^^^^^^^ not allowed in type signatures + | ^^^^^^^ error[E0747]: type provided when a constant was expected --> $DIR/synth-gen-arg-ice-158152.rs:9:26 @@ -17,5 +17,4 @@ LL | fn fn_item(_: impl Trait<{ usize }>) {} error: aborting due to 2 previous errors -Some errors have detailed explanations: E0121, E0747. -For more information about an error, try `rustc --explain E0121`. +For more information about this error, try `rustc --explain E0747`. diff --git a/tests/ui/const-generics/mgca/trait-assoc-fn-as-const-arg.rs b/tests/ui/const-generics/mgca/trait-assoc-fn-as-const-arg.rs index 7234650b39c41..167f60bf1bbf6 100644 --- a/tests/ui/const-generics/mgca/trait-assoc-fn-as-const-arg.rs +++ b/tests/ui/const-generics/mgca/trait-assoc-fn-as-const-arg.rs @@ -1,10 +1,9 @@ #![feature(min_generic_const_args)] -// Regression test for #138088. assoc functions cannot be lowered as const -// args, and this should emit a regular diagnostic instead of ICEing. +// Regression test for #138088. trait Bar { fn x(&self) -> [i32; Bar::x]; - //~^ ERROR the constant `::x` is not of type `usize` + //~^ ERROR cycle detected when evaluating type-level constant } fn main() {} diff --git a/tests/ui/const-generics/mgca/trait-assoc-fn-as-const-arg.stderr b/tests/ui/const-generics/mgca/trait-assoc-fn-as-const-arg.stderr index 9b7c42bee293c..2b26ce0db840c 100644 --- a/tests/ui/const-generics/mgca/trait-assoc-fn-as-const-arg.stderr +++ b/tests/ui/const-generics/mgca/trait-assoc-fn-as-const-arg.stderr @@ -1,10 +1,19 @@ -error: the constant `::x` is not of type `usize` - --> $DIR/trait-assoc-fn-as-const-arg.rs:6:20 +error[E0391]: cycle detected when evaluating type-level constant + --> $DIR/trait-assoc-fn-as-const-arg.rs:5:26 | LL | fn x(&self) -> [i32; Bar::x]; - | ^^^^^^^^^^^^^ expected `usize`, found fn item + | ^^^^^^ | - = note: the length of array `[i32; ::x]` must be type `usize` + = note: ...which requires const-evaluating + checking `Bar::x::{constant#0}`... + = note: ...which requires const-evaluating + checking `Bar::x::{constant#0}`... + = note: ...which requires checking if `Bar::x::{constant#0}` is a trivial const... + = note: ...which requires building MIR for `Bar::x::{constant#0}`... + = note: ...which requires match-checking `Bar::x::{constant#0}`... + = note: ...which requires type-checking `Bar::x::{constant#0}`... + = note: ...which again requires evaluating type-level constant, completing the cycle + = note: cycle used when checking that `Bar::x` is well-formed + = note: for more information, see and error: aborting due to 1 previous error +For more information about this error, try `rustc --explain E0391`. diff --git a/tests/ui/const-generics/mgca/tuple_ctor_erroneous.rs b/tests/ui/const-generics/mgca/tuple_ctor_erroneous.rs index 1eb347774f8e0..deefa8077f43f 100644 --- a/tests/ui/const-generics/mgca/tuple_ctor_erroneous.rs +++ b/tests/ui/const-generics/mgca/tuple_ctor_erroneous.rs @@ -32,7 +32,8 @@ fn test_errors() { //~| ERROR tuple constructor with invalid base path accepts_point::<{ non_ctor(N, N) }>(); - //~^ ERROR complex const arguments must be placed inside of a `const` block + //~^ ERROR function items cannot be used as const args + //~| ERROR tuple constructor with invalid base path accepts_point::<{ CONST_ITEM(N, N) }>(); //~^ ERROR tuple constructor with invalid base path diff --git a/tests/ui/const-generics/mgca/tuple_ctor_erroneous.stderr b/tests/ui/const-generics/mgca/tuple_ctor_erroneous.stderr index ce210b2b3e39a..56b11b2894aa2 100644 --- a/tests/ui/const-generics/mgca/tuple_ctor_erroneous.stderr +++ b/tests/ui/const-generics/mgca/tuple_ctor_erroneous.stderr @@ -27,20 +27,26 @@ error: tuple constructor with invalid base path LL | accepts_point::<{ UnresolvedIdent(N, N) }>(); | ^^^^^^^^^^^^^^^^^^^^^ -error: complex const arguments must be placed inside of a `const` block +error: function items cannot be used as const args + --> $DIR/tuple_ctor_erroneous.rs:34:23 + | +LL | accepts_point::<{ non_ctor(N, N) }>(); + | ^^^^^^^^ + +error: tuple constructor with invalid base path --> $DIR/tuple_ctor_erroneous.rs:34:23 | LL | accepts_point::<{ non_ctor(N, N) }>(); | ^^^^^^^^^^^^^^ error: tuple constructor with invalid base path - --> $DIR/tuple_ctor_erroneous.rs:37:23 + --> $DIR/tuple_ctor_erroneous.rs:38:23 | LL | accepts_point::<{ CONST_ITEM(N, N) }>(); | ^^^^^^^^^^^^^^^^ error: the constant `Point` is not of type `Point` - --> $DIR/tuple_ctor_erroneous.rs:40:23 + --> $DIR/tuple_ctor_erroneous.rs:41:23 | LL | accepts_point::<{ Point }>(); | ^^^^^ expected `Point`, found struct constructor @@ -52,7 +58,7 @@ LL | fn accepts_point() {} | ^^^^^^^^^^^^^^ required by this const generic parameter in `accepts_point` error: the constant `MyEnum::::Variant` is not of type `MyEnum` - --> $DIR/tuple_ctor_erroneous.rs:43:22 + --> $DIR/tuple_ctor_erroneous.rs:44:22 | LL | accepts_enum::<{ MyEnum::Variant:: }>(); | ^^^^^^^^^^^^^^^^^^^^^^ expected `MyEnum`, found enum constructor @@ -63,6 +69,6 @@ note: required by a const generic parameter in `accepts_enum` LL | fn accepts_enum>() {} | ^^^^^^^^^^^^^^^^^^^^ required by this const generic parameter in `accepts_enum` -error: aborting due to 8 previous errors +error: aborting due to 9 previous errors For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/const-generics/mgca/unexpected-fn-item-in-array.rs b/tests/ui/const-generics/mgca/unexpected-fn-item-in-array.rs index 97c1a2fb86e55..6260607d8a711 100644 --- a/tests/ui/const-generics/mgca/unexpected-fn-item-in-array.rs +++ b/tests/ui/const-generics/mgca/unexpected-fn-item-in-array.rs @@ -5,7 +5,7 @@ trait A {} impl A<[usize; fn_item]> for () {} -//~^ ERROR the constant `fn_item` is not of type `usize` +//~^ ERROR function items cannot be used as const args fn fn_item() {} diff --git a/tests/ui/const-generics/mgca/unexpected-fn-item-in-array.stderr b/tests/ui/const-generics/mgca/unexpected-fn-item-in-array.stderr index de64bf7b74c01..eb19e563abb88 100644 --- a/tests/ui/const-generics/mgca/unexpected-fn-item-in-array.stderr +++ b/tests/ui/const-generics/mgca/unexpected-fn-item-in-array.stderr @@ -1,10 +1,8 @@ -error: the constant `fn_item` is not of type `usize` - --> $DIR/unexpected-fn-item-in-array.rs:7:6 +error: function items cannot be used as const args + --> $DIR/unexpected-fn-item-in-array.rs:7:16 | LL | impl A<[usize; fn_item]> for () {} - | ^^^^^^^^^^^^^^^^^^^ expected `usize`, found fn item - | - = note: the length of array `[usize; fn_item]` must be type `usize` + | ^^^^^^^ error: aborting due to 1 previous error From 8242b333dab98c8f60b08be6dd397cbd0d3bf4d0 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 22 Jul 2026 16:18:51 +0200 Subject: [PATCH 40/61] document #[global_allocator] constraints --- library/alloc/src/alloc.rs | 85 ++++++++++++++++ library/core/src/alloc/global.rs | 97 +++++++++++++------ library/std/src/alloc.rs | 5 + src/tools/miri/tests/pass/global_allocator.rs | 13 ++- 4 files changed, 169 insertions(+), 31 deletions(-) diff --git a/library/alloc/src/alloc.rs b/library/alloc/src/alloc.rs index 8560a644f207b..49bf941984af5 100644 --- a/library/alloc/src/alloc.rs +++ b/library/alloc/src/alloc.rs @@ -64,6 +64,28 @@ pub struct Global; /// of the allocator registered with the `#[global_allocator]` attribute /// if there is one, or the `std` crate’s default. /// +/// Note, however, that invoking this function is *not* equivalent to invoking the underlying +/// [`GlobalAlloc::alloc`] method of the registered allocator directly. Users of this function +/// cannot assume anything about what the allocator does, other than the documented requirements. +/// This means: +/// +/// - This function may non-deterministically entirely skip the underlying allocator, e.g. if the +/// compiler can show that this allocation can be replaced by a stack variable. The compiler may +/// also merge multiple allocation operations into one, as long as it can also adjust all +/// corresponding deallocation operations accordingly. +/// - An allocation created by invoking this function has exactly the size and minimum alignment +/// defined by `layout`, even if the underlying allocator makes stronger promises. +/// - The allocation can only be freed by invoking [`dealloc`] or [`realloc`]. In particular, +/// passing a pointer to such an allocation directly to the underlying method on [`GlobalAlloc`] is +/// not permitted. Until one of those functions is called, it is undefined behavior to access the +/// memory that backs this allocation with any pointer not derived from the return value of this +/// function (e.g., with internal pointers the allocator might keep around). +/// - This function de-initializes the contents of the allocation before handing it to the user. So even +/// if you control the underlying allocator and know that it explicitly initialized this memory, +/// you cannot rely on it being initialized. +/// +/// Users of this function have to consider that in the future, allocators may be allowed to unwind. +/// /// This function is expected to be deprecated in favor of the `allocate` method /// of the [`Global`] type when it and the [`Allocator`] trait become stable. /// @@ -109,6 +131,24 @@ pub unsafe fn alloc(layout: Layout) -> *mut u8 { /// of the allocator registered with the `#[global_allocator]` attribute /// if there is one, or the `std` crate’s default. /// +/// Note, however, that invoking this function is *not* equivalent to invoking the underlying +/// [`GlobalAlloc::dealloc`] method of the registered allocator directly. Users of this function +/// cannot assume anything about what the allocator does, other than the documented requirements. +/// This means: +/// +/// - This function may non-deterministically entirely skip the underlying allocator, e.g. if the +/// compiler can show that this allocation can be replaced by a stack variable. The compiler may +/// also merge multiple allocation operations into one, as long as it can also adjust all +/// corresponding deallocation operations accordingly. +/// - The pointer passed to this function must have been obtained by invoking [`alloc`], +/// [`alloc_zeroed`], or [`realloc`]. In particular, passing a pointer returned by the underlying +/// methods on [`GlobalAlloc`] is not permitted. +/// - This function de-initializes the contents of the allocation before handing it to the allocator. +/// So even if you know that the program previously initialized that memory, the allocator cannot +/// rely on it being initialized. +/// +/// Users of this function have to consider that in the future, allocators may be allowed to unwind. +/// /// This function is expected to be deprecated in favor of the `deallocate` method /// of the [`Global`] type when it and the [`Allocator`] trait become stable. /// @@ -135,6 +175,32 @@ unsafe fn dealloc_nonnull(ptr: NonNull, layout: Layout) { /// of the allocator registered with the `#[global_allocator]` attribute /// if there is one, or the `std` crate’s default. /// +/// Note, however, that invoking this function is *not* equivalent to invoking the underlying +/// [`GlobalAlloc::realloc`] method of the registered allocator directly. Users of this function +/// cannot assume anything about what the allocator does, other than the documented requirements. +/// This means: +/// +/// - This function may non-deterministically entirely skip the underlying allocator, e.g. if the +/// compiler can show that this allocation can be replaced by a stack variable. The compiler may +/// also merge multiple allocation operations into one, as long as it can also adjust all +/// corresponding deallocation operations accordingly. +/// - The pointer passed to this function must have been obtained by invoking [`alloc`], +/// [`alloc_zeroed`], or [`realloc`]. In particular, passing a pointer returned by the underlying +/// methods on [`GlobalAlloc`] is not permitted. +/// - An allocation created by invoking this function has exactly the size and minimum alignment +/// defined by `layout`, even if the underlying allocator makes stronger promises. +/// - The allocation can only be freed by invoking [`dealloc`] or [`realloc`]. In particular, +/// passing a pointer to such an allocation directly to the underlying method on [`GlobalAlloc`] is +/// not permitted. Until one of those functions is called, it is undefined behavior to access the +/// memory that backs this allocation with any pointer not derived from the return value of this +/// function (e.g., with internal pointers the allocator might keep around). +/// - If this grows the allocation, the contents of the grown part of the new allocation allocation +/// are de-initialized by this function before returning. +/// - If this shrinks the allocation, the contents of the removed part of the old allocation are +/// de-initialized by this function before invoking the underlying allocator. +/// +/// Users of this function have to consider that in the future, allocators may be allowed to unwind. +/// /// This function is expected to be deprecated in favor of the `grow` and `shrink` methods /// of the [`Global`] type when it and the [`Allocator`] trait become stable. /// @@ -162,6 +228,25 @@ unsafe fn realloc_nonnull(ptr: NonNull, layout: Layout, new_size: usize) -> /// of the allocator registered with the `#[global_allocator]` attribute /// if there is one, or the `std` crate’s default. /// +/// Note, however, that invoking this function is *not* equivalent to invoking the underlying +/// [`GlobalAlloc::alloc_zeroed`] method of the registered allocator directly. Users of this +/// function cannot assume anything about what the allocator does, other than the documented +/// requirements. This means: +/// +/// - This function may non-deterministically entirely skip the underlying allocator, e.g. if the +/// compiler can show that this allocation can be replaced by a stack variable. The compiler may +/// also merge multiple allocation operations into one, as long as it can also adjust all +/// corresponding deallocation operations accordingly. +/// - The allocation can only be freed by invoking [`dealloc`] or [`realloc`]. In particular, +/// passing a pointer to such an allocation directly to the underlying method on [`GlobalAlloc`] is +/// not permitted. Until one of those functions is called, it is undefined behavior to access the +/// memory that backs this allocation with any pointer not derived from the return value of this +/// function (e.g., with internal pointers the allocator might keep around). +/// - An allocation created by invoking this function has exactly the size and minimum alignment +/// defined by `layout`, even if the underlying allocator makes stronger promises. +/// +/// Users of this function have to consider that in the future, allocators may be allowed to unwind. +/// /// This function is expected to be deprecated in favor of the `allocate_zeroed` method /// of the [`Global`] type when it and the [`Allocator`] trait become stable. /// diff --git a/library/core/src/alloc/global.rs b/library/core/src/alloc/global.rs index 44a8ab6e54196..0036b7a55c8ce 100644 --- a/library/core/src/alloc/global.rs +++ b/library/core/src/alloc/global.rs @@ -19,7 +19,6 @@ use crate::{cmp, ptr}; /// method such as `dealloc` or by being /// passed to a reallocation method that returns a non-null pointer. /// -/// /// # Example /// /// ```standalone_crate @@ -85,39 +84,83 @@ use crate::{cmp, ptr}; /// } /// ``` /// +/// # The `#[global_allocator]` attribute +/// +/// As the example above demonstrates, the `#[global_allocator]` attribute can be used to register a +/// concrete `static` of a type that implements this trait to become *the* global allocator +/// for the current program. That global allocator can be invoked via the functions [`alloc`], +/// [`alloc_zeroed`], [`dealloc`], [`realloc`]). Note, however, that invoking those functions is +/// *not* equivalent to directly invoking the underlying methods on the declared global allocator! +/// Users of the global allocator cannot assume anything about what the allocator does (even if they know which allocator is being used), +/// and implementors of the allocator cannot assume anything about what the program does (even if they know how the allocator is being used). +/// Both can only assume the documented requirements for the respective other party of this contract. +/// This means: +/// +/// - Allocation functions may non-deterministically entirely skip the underlying allocator, e.g. if the +/// compiler can show that this allocation can be replaced by a stack variable. The compiler may +/// also merge multiple allocation operations into one, as long as it can also adjust all +/// corresponding deallocation operations accordingly. +/// - An allocation created by invoking [`alloc`], [`alloc_zeroed`], or [`realloc`] has exactly the +/// size and minimum alignment defined by `layout`, even if the underlying allocator makes +/// stronger promises. +/// - An allocation created by invoking [`alloc`], [`alloc_zeroed`], or [`realloc`] can only be +/// freed by invoking [`dealloc`] or [`realloc`]. In particular, passing a pointer to such an +/// allocation directly to the underlying method on [`GlobalAlloc`] is not permitted. Until one of +/// those functions is called, it is undefined behavior to access the memory that backs this +/// allocation with any pointer not derived from the return value of this function (e.g., with +/// internal pointers the allocator might keep around). +/// - The pointer passed to [`dealloc`] or [`realloc`] must have been obtained by invoking [`alloc`], +/// [`alloc_zeroed`], or [`realloc`]. In particular, passing a pointer returned by the underlying +/// methods on [`GlobalAlloc`] is not permitted. +/// - [`alloc`] de-initializes the contents of the allocation before handing it to the user. So even +/// if you control the underlying allocator and know that it explicitly initialized this memory, +/// you cannot rely on it being initialized. For a [`realloc`] that grows an allocation, this +/// applies to the newly allocated part. +/// - [`dealloc`] de-initializes the contents of the allocation before handing it to the allocator. +/// So even if you know that the program previously initialized that memory, the allocator cannot +/// rely on it being initialized. For a [`realloc`] that shrinks an allocation, this applies to +/// the part being removed. +/// +/// [`alloc`]: ../../std/alloc/fn.alloc.html +/// [`alloc_zeroed`]: ../../std/alloc/fn.alloc_zeroed.html +/// [`dealloc`]: ../../std/alloc/fn.dealloc.html +/// [`realloc`]: ../../std/alloc/fn.realloc.html +/// +/// The first point means that you cannot rely on global allocations actually happening, even if +/// there are explicit global allocations in the source. The optimizer may detect unused global +/// allocations that it can either eliminate entirely or move to the stack and thus never invoke the +/// global allocator. The optimizer may further assume that allocation is infallible, so code that +/// used to fail due to allocator failures may now suddenly work because the optimizer worked around +/// the need for an allocation. More concretely, the following code example is unsound, irrespective +/// of whether your custom allocator allows counting how many allocations have happened. +/// +/// ```rust,ignore (unsound and has placeholders) +/// drop(Box::new(42)); +/// let number_of_heap_allocs = /* call private allocator API */; +/// unsafe { std::hint::assert_unchecked(number_of_heap_allocs > 0); } +/// ``` +/// +/// Note that the optimizations mentioned above are not the only +/// optimization that can be applied. You may generally not rely on global allocations +/// happening if they can be removed without changing program behavior. +/// Whether allocations happen or not is not part of the program behavior, even if it +/// could be detected via an allocator that tracks allocations by printing or otherwise +/// having side effects. +/// /// # Safety /// /// The `GlobalAlloc` trait is an `unsafe` trait for a number of reasons, and /// implementors must ensure that they adhere to these contracts: /// +/// * It is undefined behavior for the allocator to read, write, or deallocate any memory that +/// is *currently allocated*. This memory is owned by the user, the allocator must not touch it. +/// /// * It's undefined behavior if global allocators unwind. This restriction may /// be lifted in the future, but currently a panic from any of these /// functions may lead to memory unsafety. /// -/// * `Layout` queries and calculations in general must be correct. Callers of -/// this trait are allowed to rely on the contracts defined on each method, -/// and implementors must ensure such contracts remain true. -/// -/// * You must not rely on allocations actually happening, even if there are explicit -/// heap allocations in the source. The optimizer may detect unused allocations that it can either -/// eliminate entirely or move to the stack and thus never invoke the allocator. The -/// optimizer may further assume that allocation is infallible, so code that used to fail due -/// to allocator failures may now suddenly work because the optimizer worked around the -/// need for an allocation. More concretely, the following code example is unsound, irrespective -/// of whether your custom allocator allows counting how many allocations have happened. -/// -/// ```rust,ignore (unsound and has placeholders) -/// drop(Box::new(42)); -/// let number_of_heap_allocs = /* call private allocator API */; -/// unsafe { std::hint::assert_unchecked(number_of_heap_allocs > 0); } -/// ``` -/// -/// Note that the optimizations mentioned above are not the only -/// optimization that can be applied. You may generally not rely on heap allocations -/// happening if they can be removed without changing program behavior. -/// Whether allocations happen or not is not part of the program behavior, even if it -/// could be detected via an allocator that tracks allocations by printing or otherwise -/// having side effects. +/// * Callers of this trait are allowed to rely on the contracts defined on each method, and +/// implementors must ensure such contracts remain true. /// /// # Re-entrance /// @@ -133,7 +176,7 @@ use crate::{cmp, ptr}; /// - [`std::thread_local`], /// - [`std::thread::current`], /// - [`std::thread::park`] and [`std::thread::Thread`]'s [`unpark`] method and -/// [`Clone`] implementation. +/// [`Clone`] implementation. /// /// [`std`]: ../../std/index.html /// [`std::sync::Mutex`]: ../../std/sync/struct.Mutex.html @@ -174,7 +217,7 @@ pub unsafe trait GlobalAlloc { /// /// Clients wishing to abort computation in response to an /// allocation error are encouraged to call the [`handle_alloc_error`] function, - /// rather than directly invoking `panic!` or similar. + /// rather than directly invoking `panic!` or similar (but note that both may unwind). /// /// [`handle_alloc_error`]: ../../alloc/alloc/fn.handle_alloc_error.html #[stable(feature = "global_alloc", since = "1.28.0")] diff --git a/library/std/src/alloc.rs b/library/std/src/alloc.rs index 84447c06aaecf..9d39fbf770e91 100644 --- a/library/std/src/alloc.rs +++ b/library/std/src/alloc.rs @@ -53,6 +53,11 @@ //! The `#[global_allocator]` can only be used once in a crate //! or its recursive dependencies. //! +//! The global allocator is invoked via the functions in this module +//! ([`alloc`][crate::alloc::alloc], [`alloc_zeroed`], [`dealloc`], [`realloc`]). Note, however, +//! that invoking those functions is *not* equivalent to directly invoking the underlying methods on +//! the declared global allocator! See the documentation of those functions for details. +//! //! [^system-alloc]: Note that the Rust standard library internals may still //! directly call [`System`] when necessary (for example for the runtime //! support typically required to implement a global allocator, see [re-entrance] on [`GlobalAlloc`] diff --git a/src/tools/miri/tests/pass/global_allocator.rs b/src/tools/miri/tests/pass/global_allocator.rs index 9a40c322b366e..ecb3a5e46e3a4 100644 --- a/src/tools/miri/tests/pass/global_allocator.rs +++ b/src/tools/miri/tests/pass/global_allocator.rs @@ -7,10 +7,12 @@ static ALLOCATOR: Allocator = Allocator; struct Allocator; +const SIZE: usize = 16 * 7; + unsafe impl GlobalAlloc for Allocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { // use specific size to avoid getting triggered by rt - if layout.size() == 123 { + if layout.size() == SIZE { println!("Allocated!") } @@ -18,7 +20,7 @@ unsafe impl GlobalAlloc for Allocator { } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - if layout.size() == 123 { + if layout.size() == SIZE { println!("Deallocated!") } @@ -27,13 +29,16 @@ unsafe impl GlobalAlloc for Allocator { } fn main() { - // Only okay because we explicitly set a global allocator that uses the system allocator! - let l = Layout::from_size_align(123, 1).unwrap(); + // Below we mix using `Global` and `System`. This is undefined behavior that Miri should + // detect, but currently it does not. + + let l = Layout::from_size_align(SIZE, 1).unwrap(); let ptr = Global.allocate(l).unwrap().as_non_null_ptr(); // allocating with Global... unsafe { System.deallocate(ptr, l); } // ... and deallocating with System. + let l = Layout::from_size_align(SIZE, 16).unwrap(); let ptr = System.allocate(l).unwrap().as_non_null_ptr(); // allocating with System... unsafe { Global.deallocate(ptr, l); From 73ff59fce9a5aea27477c27cc6ccc4555325ce6e Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Thu, 24 Oct 2024 06:22:18 +0100 Subject: [PATCH 41/61] Implement asm_const_ptr feature The backend now fully supports codegen of const pointers, remove the block inside typeck behind a new feature gate. Tests are also added. --- compiler/rustc_feature/src/unstable.rs | 2 + compiler/rustc_hir_typeck/src/diagnostics.rs | 7 +++ compiler/rustc_hir_typeck/src/inline_asm.rs | 35 +++++++++++++- compiler/rustc_span/src/symbol.rs | 1 + tests/assembly-llvm/asm/global_asm.rs | 5 ++ tests/assembly-llvm/asm/x86-types.rs | 11 ++++- tests/ui/asm/const-refs-to-static.rs | 9 ++-- tests/ui/asm/const-refs-to-static.stderr | 22 --------- tests/ui/asm/invalid-const-operand.rs | 12 +++-- tests/ui/asm/invalid-const-operand.stderr | 46 ++++++------------- .../feature-gate-asm_const_ptr.rs | 22 +++++++++ .../feature-gate-asm_const_ptr.stderr | 33 +++++++++++++ 12 files changed, 140 insertions(+), 65 deletions(-) delete mode 100644 tests/ui/asm/const-refs-to-static.stderr create mode 100644 tests/ui/feature-gates/feature-gate-asm_const_ptr.rs create mode 100644 tests/ui/feature-gates/feature-gate-asm_const_ptr.stderr diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 86cc8bb56c6b5..0e762481874ef 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -398,6 +398,8 @@ declare_features! ( (unstable, arbitrary_self_types_pointers, "1.83.0", Some(44874)), /// Target features on arm. (unstable, arm_target_feature, "1.27.0", Some(150246)), + /// Allows using `const` operands with pointer in inline assembly. + (unstable, asm_const_ptr, "CURRENT_RUSTC_VERSION", Some(128464)), /// Enables experimental inline assembly support for additional architectures. (unstable, asm_experimental_arch, "1.58.0", Some(93335)), /// Enables experimental register support in inline assembly. diff --git a/compiler/rustc_hir_typeck/src/diagnostics.rs b/compiler/rustc_hir_typeck/src/diagnostics.rs index a5c39bd1584a1..1a6df92957d00 100644 --- a/compiler/rustc_hir_typeck/src/diagnostics.rs +++ b/compiler/rustc_hir_typeck/src/diagnostics.rs @@ -18,6 +18,13 @@ use rustc_span::{Ident, Span, Spanned, Symbol}; use crate::FnCtxt; +#[derive(Diagnostic)] +#[diag("using pointers in asm `const` operand is experimental")] +pub(crate) struct AsmConstPtrUnstable { + #[primary_span] + pub span: Span, +} + #[derive(Diagnostic)] #[diag("base expression required after `..`", code = E0797)] pub(crate) struct BaseExpressionDoubleDot { diff --git a/compiler/rustc_hir_typeck/src/inline_asm.rs b/compiler/rustc_hir_typeck/src/inline_asm.rs index 9dfbcd9dda760..b720a75303c47 100644 --- a/compiler/rustc_hir_typeck/src/inline_asm.rs +++ b/compiler/rustc_hir_typeck/src/inline_asm.rs @@ -17,7 +17,7 @@ use rustc_target::asm::{ use rustc_trait_selection::infer::InferCtxtExt; use crate::FnCtxt; -use crate::diagnostics::RegisterTypeUnstable; +use crate::diagnostics::{AsmConstPtrUnstable, RegisterTypeUnstable}; pub(crate) struct InlineAsmCtxt<'a, 'tcx> { target_features: &'tcx FxIndexSet, @@ -548,7 +548,36 @@ impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> { match ty.kind() { ty::Error(_) => {} _ if ty.is_integral() => {} + ty::FnPtr(..) => { + if !self.tcx().features().asm_const_ptr() { + self.tcx() + .sess + .create_feature_err( + AsmConstPtrUnstable { span: op_sp }, + sym::asm_const_ptr, + ) + .emit(); + } + } + ty::RawPtr(pointee, _) | ty::Ref(_, pointee, _) + if self.is_thin_ptr_ty(*pointee) => + { + if !self.tcx().features().asm_const_ptr() { + self.tcx() + .sess + .create_feature_err( + AsmConstPtrUnstable { span: op_sp }, + sym::asm_const_ptr, + ) + .emit(); + } + } _ => { + let const_possible_ty = if !self.tcx().features().asm_const_ptr() { + "integer" + } else { + "integer or thin pointer" + }; self.fcx .dcx() .struct_span_err(op_sp, "invalid type for `const` operand") @@ -556,7 +585,9 @@ impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> { self.tcx().def_span(anon_const.def_id), format!("is {} `{}`", ty.kind().article(), ty), ) - .with_help("`const` operands must be of an integer type") + .with_help(format!( + "`const` operands must be of an {const_possible_ty} type" + )) .emit(); } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 7917478cc2c60..50bc93fe5ea2e 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -426,6 +426,7 @@ symbols! { asm, asm_cfg, asm_const, + asm_const_ptr, asm_experimental_arch, asm_experimental_reg, asm_goto, diff --git a/tests/assembly-llvm/asm/global_asm.rs b/tests/assembly-llvm/asm/global_asm.rs index 8a4bf98c7450b..3a7641cc7f76f 100644 --- a/tests/assembly-llvm/asm/global_asm.rs +++ b/tests/assembly-llvm/asm/global_asm.rs @@ -5,6 +5,7 @@ //@ compile-flags: -C symbol-mangling-version=v0 #![crate_type = "rlib"] +#![feature(asm_const_ptr)] use std::arch::global_asm; @@ -26,6 +27,10 @@ global_asm!("call {}", sym my_func); global_asm!("lea rax, [rip + {}]", sym MY_STATIC); // CHECK: call _RNvC[[CRATE_IDENT:[a-zA-Z0-9]{12}]]_10global_asm6foobar global_asm!("call {}", sym foobar); +// CHECK: lea rax, [rip + _RNSC[[CRATE_IDENT]]_10global_asm3cgu.0] +global_asm!("lea rax, [rip + {}]", const &1); +// CHECK: lea rax, [rip + _RNSC[[CRATE_IDENT]]_10global_asm3cgu.1+4] +global_asm!("lea rax, [rip + {}]", const &[1; 2][1]); // CHECK: _RNvC[[CRATE_IDENT]]_10global_asm6foobar: fn foobar() { loop {} diff --git a/tests/assembly-llvm/asm/x86-types.rs b/tests/assembly-llvm/asm/x86-types.rs index 6e52ab260569d..ac778f6320c28 100644 --- a/tests/assembly-llvm/asm/x86-types.rs +++ b/tests/assembly-llvm/asm/x86-types.rs @@ -9,7 +9,7 @@ //@ compile-flags: -C target-feature=+avx512bw //@ compile-flags: -Zmerge-functions=disabled -#![feature(no_core, f16, f128)] +#![feature(no_core, f16, f128, asm_const_ptr)] #![crate_type = "rlib"] #![no_core] #![allow(asm_sub_register, non_camel_case_types)] @@ -43,6 +43,15 @@ pub unsafe fn sym_static() { asm!("mov al, byte ptr [{}]", sym extern_static); } +// CHECK-LABEL: const_ptr: +// CHECK: #APP +// CHECK: mov al, byte ptr [{{.*}}anon{{.*}}] +// CHECK: #NO_APP +#[no_mangle] +pub unsafe fn const_ptr() { + asm!("mov al, byte ptr [{}]", const &1u8); +} + macro_rules! check { ($func:ident $ty:ident $class:ident $mov:literal) => { #[no_mangle] diff --git a/tests/ui/asm/const-refs-to-static.rs b/tests/ui/asm/const-refs-to-static.rs index ce2c5b3246ec8..8058d70550aba 100644 --- a/tests/ui/asm/const-refs-to-static.rs +++ b/tests/ui/asm/const-refs-to-static.rs @@ -1,19 +1,20 @@ //@ needs-asm-support //@ ignore-nvptx64 //@ ignore-spirv +//@ build-pass + +#![feature(asm_const_ptr)] use std::arch::{asm, global_asm}; use std::ptr::addr_of; static FOO: u8 = 42; -global_asm!("{}", const addr_of!(FOO)); -//~^ ERROR invalid type for `const` operand +global_asm!("/* {} */", const addr_of!(FOO)); #[no_mangle] fn inline() { - unsafe { asm!("{}", const addr_of!(FOO)) }; - //~^ ERROR invalid type for `const` operand + unsafe { asm!("/* {} */", const addr_of!(FOO)) }; } fn main() {} diff --git a/tests/ui/asm/const-refs-to-static.stderr b/tests/ui/asm/const-refs-to-static.stderr deleted file mode 100644 index 10e1ca5bd6068..0000000000000 --- a/tests/ui/asm/const-refs-to-static.stderr +++ /dev/null @@ -1,22 +0,0 @@ -error: invalid type for `const` operand - --> $DIR/const-refs-to-static.rs:10:19 - | -LL | global_asm!("{}", const addr_of!(FOO)); - | ^^^^^^------------- - | | - | is a `*const u8` - | - = help: `const` operands must be of an integer type - -error: invalid type for `const` operand - --> $DIR/const-refs-to-static.rs:15:25 - | -LL | unsafe { asm!("{}", const addr_of!(FOO)) }; - | ^^^^^^------------- - | | - | is a `*const u8` - | - = help: `const` operands must be of an integer type - -error: aborting due to 2 previous errors - diff --git a/tests/ui/asm/invalid-const-operand.rs b/tests/ui/asm/invalid-const-operand.rs index 5c7b1a6b9654f..ab25b8e3db6a7 100644 --- a/tests/ui/asm/invalid-const-operand.rs +++ b/tests/ui/asm/invalid-const-operand.rs @@ -3,6 +3,8 @@ //@ ignore-spirv //@ reference: asm.operand-type.supported-operands.const +#![feature(asm_const_ptr)] + use std::arch::{asm, global_asm}; // Const operands must be integers and must be constants. @@ -13,11 +15,10 @@ global_asm!("{}", const 0i128); global_asm!("{}", const 0f32); //~^ ERROR invalid type for `const` operand global_asm!("{}", const 0 as *mut u8); -//~^ ERROR invalid type for `const` operand fn test1() { unsafe { - // Const operands must be integers and must be constants. + // Const operands must be integers or thin pointers asm!("{}", const 0); asm!("{}", const 0i32); @@ -25,9 +26,14 @@ fn test1() { asm!("{}", const 0f32); //~^ ERROR invalid type for `const` operand asm!("{}", const 0 as *mut u8); - //~^ ERROR invalid type for `const` operand asm!("{}", const &0); + asm!("{}", const b"Foo".as_slice()); //~^ ERROR invalid type for `const` operand + + asm!("{}", const test1 as fn()); + asm!("{}", const test1); + asm!("{}", const (|| {}) as fn()); + asm!("{}", const || {}); } } diff --git a/tests/ui/asm/invalid-const-operand.stderr b/tests/ui/asm/invalid-const-operand.stderr index 3a3129ff3f6be..dc6378ff571e4 100644 --- a/tests/ui/asm/invalid-const-operand.stderr +++ b/tests/ui/asm/invalid-const-operand.stderr @@ -1,5 +1,5 @@ error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/invalid-const-operand.rs:45:26 + --> $DIR/invalid-const-operand.rs:51:26 | LL | asm!("{}", const x); | ^ non-constant value @@ -11,7 +11,7 @@ LL + const x: /* Type */ = 0; | error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/invalid-const-operand.rs:48:36 + --> $DIR/invalid-const-operand.rs:54:36 | LL | asm!("{}", const const_foo(x)); | ^ non-constant value @@ -23,7 +23,7 @@ LL + const x: /* Type */ = 0; | error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/invalid-const-operand.rs:51:36 + --> $DIR/invalid-const-operand.rs:57:36 | LL | asm!("{}", const const_bar(x)); | ^ non-constant value @@ -35,55 +35,35 @@ LL + const x: /* Type */ = 0; | error: invalid type for `const` operand - --> $DIR/invalid-const-operand.rs:13:19 + --> $DIR/invalid-const-operand.rs:15:19 | LL | global_asm!("{}", const 0f32); | ^^^^^^---- | | | is an `f32` | - = help: `const` operands must be of an integer type - -error: invalid type for `const` operand - --> $DIR/invalid-const-operand.rs:15:19 - | -LL | global_asm!("{}", const 0 as *mut u8); - | ^^^^^^------------ - | | - | is a `*mut u8` - | - = help: `const` operands must be of an integer type + = help: `const` operands must be of an integer or thin pointer type error: invalid type for `const` operand - --> $DIR/invalid-const-operand.rs:25:20 + --> $DIR/invalid-const-operand.rs:26:20 | LL | asm!("{}", const 0f32); | ^^^^^^---- | | | is an `f32` | - = help: `const` operands must be of an integer type - -error: invalid type for `const` operand - --> $DIR/invalid-const-operand.rs:27:20 - | -LL | asm!("{}", const 0 as *mut u8); - | ^^^^^^------------ - | | - | is a `*mut u8` - | - = help: `const` operands must be of an integer type + = help: `const` operands must be of an integer or thin pointer type error: invalid type for `const` operand - --> $DIR/invalid-const-operand.rs:29:20 + --> $DIR/invalid-const-operand.rs:30:20 | -LL | asm!("{}", const &0); - | ^^^^^^-- +LL | asm!("{}", const b"Foo".as_slice()); + | ^^^^^^----------------- | | - | is a `&i32` + | is a `&[u8]` | - = help: `const` operands must be of an integer type + = help: `const` operands must be of an integer or thin pointer type -error: aborting due to 8 previous errors +error: aborting due to 6 previous errors For more information about this error, try `rustc --explain E0435`. diff --git a/tests/ui/feature-gates/feature-gate-asm_const_ptr.rs b/tests/ui/feature-gates/feature-gate-asm_const_ptr.rs new file mode 100644 index 0000000000000..cdcb5995a0f08 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-asm_const_ptr.rs @@ -0,0 +1,22 @@ +//@ only-x86_64 + +use std::arch::{asm, global_asm, naked_asm}; + +global_asm!("/* {} */", const &0); +//~^ ERROR using pointers in asm `const` operand is experimental + +#[unsafe(naked)] +extern "C" fn naked() { + unsafe { + naked_asm!("ret /* {} */", const &0); + //~^ ERROR using pointers in asm `const` operand is experimental + } +} + +fn main() { + naked(); + unsafe { + asm!("/* {} */", const &0); + //~^ ERROR using pointers in asm `const` operand is experimental + } +} diff --git a/tests/ui/feature-gates/feature-gate-asm_const_ptr.stderr b/tests/ui/feature-gates/feature-gate-asm_const_ptr.stderr new file mode 100644 index 0000000000000..a804d8fe44be5 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-asm_const_ptr.stderr @@ -0,0 +1,33 @@ +error[E0658]: using pointers in asm `const` operand is experimental + --> $DIR/feature-gate-asm_const_ptr.rs:5:25 + | +LL | global_asm!("/* {} */", const &0); + | ^^^^^^^^ + | + = note: see issue #128464 for more information + = help: add `#![feature(asm_const_ptr)]` 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]: using pointers in asm `const` operand is experimental + --> $DIR/feature-gate-asm_const_ptr.rs:11:36 + | +LL | naked_asm!("ret /* {} */", const &0); + | ^^^^^^^^ + | + = note: see issue #128464 for more information + = help: add `#![feature(asm_const_ptr)]` 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]: using pointers in asm `const` operand is experimental + --> $DIR/feature-gate-asm_const_ptr.rs:19:26 + | +LL | asm!("/* {} */", const &0); + | ^^^^^^^^ + | + = note: see issue #128464 for more information + = help: add `#![feature(asm_const_ptr)]` 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 3 previous errors + +For more information about this error, try `rustc --explain E0658`. From 5d22dacf52491d44c1a94275b802927661085557 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 22 Apr 2026 19:58:35 +0100 Subject: [PATCH 42/61] Add test if asm const are MIR inlined and monomorphized twice --- tests/ui/asm/asm-const-ptr-mir-inline.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/ui/asm/asm-const-ptr-mir-inline.rs diff --git a/tests/ui/asm/asm-const-ptr-mir-inline.rs b/tests/ui/asm/asm-const-ptr-mir-inline.rs new file mode 100644 index 0000000000000..428825ac34412 --- /dev/null +++ b/tests/ui/asm/asm-const-ptr-mir-inline.rs @@ -0,0 +1,17 @@ +//@ build-pass +//@ needs-asm-support + +#![feature(asm_const_ptr)] + +// Force inline to exercise the codegen when the same asm const ptr is code-generated multiple +// times. +#[inline(always)] +fn foo() { + unsafe{core::arch::asm!("/* {} */", const &N)}; +} + +fn main(){ + foo::<0>(); + foo::<0>(); + foo::<1>(); +} From 031608540852b5a32d4a8984a66a6826378afd06 Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Wed, 22 Jul 2026 21:16:46 +0100 Subject: [PATCH 43/61] Expand invalid-const-operand test case to cover &str and &CStr Use edition 2021 so c-string literals are supported. --- tests/ui/asm/invalid-const-operand.rs | 5 ++++ tests/ui/asm/invalid-const-operand.stderr | 34 ++++++++++++++++++----- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/tests/ui/asm/invalid-const-operand.rs b/tests/ui/asm/invalid-const-operand.rs index ab25b8e3db6a7..083d4f4e18480 100644 --- a/tests/ui/asm/invalid-const-operand.rs +++ b/tests/ui/asm/invalid-const-operand.rs @@ -1,3 +1,4 @@ +//@ edition:2021 //@ needs-asm-support //@ ignore-nvptx64 //@ ignore-spirv @@ -29,6 +30,10 @@ fn test1() { asm!("{}", const &0); asm!("{}", const b"Foo".as_slice()); //~^ ERROR invalid type for `const` operand + asm!("{}", const "Foo"); + //~^ ERROR invalid type for `const` operand + asm!("{}", const c"Foo"); + //~^ ERROR invalid type for `const` operand asm!("{}", const test1 as fn()); asm!("{}", const test1); diff --git a/tests/ui/asm/invalid-const-operand.stderr b/tests/ui/asm/invalid-const-operand.stderr index dc6378ff571e4..c4fa69a095e53 100644 --- a/tests/ui/asm/invalid-const-operand.stderr +++ b/tests/ui/asm/invalid-const-operand.stderr @@ -1,5 +1,5 @@ error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/invalid-const-operand.rs:51:26 + --> $DIR/invalid-const-operand.rs:56:26 | LL | asm!("{}", const x); | ^ non-constant value @@ -11,7 +11,7 @@ LL + const x: /* Type */ = 0; | error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/invalid-const-operand.rs:54:36 + --> $DIR/invalid-const-operand.rs:59:36 | LL | asm!("{}", const const_foo(x)); | ^ non-constant value @@ -23,7 +23,7 @@ LL + const x: /* Type */ = 0; | error[E0435]: attempt to use a non-constant value in a constant - --> $DIR/invalid-const-operand.rs:57:36 + --> $DIR/invalid-const-operand.rs:62:36 | LL | asm!("{}", const const_bar(x)); | ^ non-constant value @@ -35,7 +35,7 @@ LL + const x: /* Type */ = 0; | error: invalid type for `const` operand - --> $DIR/invalid-const-operand.rs:15:19 + --> $DIR/invalid-const-operand.rs:16:19 | LL | global_asm!("{}", const 0f32); | ^^^^^^---- @@ -45,7 +45,7 @@ LL | global_asm!("{}", const 0f32); = help: `const` operands must be of an integer or thin pointer type error: invalid type for `const` operand - --> $DIR/invalid-const-operand.rs:26:20 + --> $DIR/invalid-const-operand.rs:27:20 | LL | asm!("{}", const 0f32); | ^^^^^^---- @@ -55,7 +55,7 @@ LL | asm!("{}", const 0f32); = help: `const` operands must be of an integer or thin pointer type error: invalid type for `const` operand - --> $DIR/invalid-const-operand.rs:30:20 + --> $DIR/invalid-const-operand.rs:31:20 | LL | asm!("{}", const b"Foo".as_slice()); | ^^^^^^----------------- @@ -64,6 +64,26 @@ LL | asm!("{}", const b"Foo".as_slice()); | = help: `const` operands must be of an integer or thin pointer type -error: aborting due to 6 previous errors +error: invalid type for `const` operand + --> $DIR/invalid-const-operand.rs:33:20 + | +LL | asm!("{}", const "Foo"); + | ^^^^^^----- + | | + | is a `&str` + | + = help: `const` operands must be of an integer or thin pointer type + +error: invalid type for `const` operand + --> $DIR/invalid-const-operand.rs:35:20 + | +LL | asm!("{}", const c"Foo"); + | ^^^^^^------ + | | + | is a `&CStr` + | + = help: `const` operands must be of an integer or thin pointer type + +error: aborting due to 8 previous errors For more information about this error, try `rustc --explain E0435`. From a14d50a7b6f516bfaeaf2a764c3e40879a677093 Mon Sep 17 00:00:00 2001 From: AayushMainali-Github Date: Thu, 23 Jul 2026 18:34:36 +0000 Subject: [PATCH 44/61] rustdoc-js: ignore editor temp files in test folder discovery --- src/tools/rustdoc-js/tester.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/tools/rustdoc-js/tester.js b/src/tools/rustdoc-js/tester.js index aaf40ceeadf71..9985fa1d7bcd5 100644 --- a/src/tools/rustdoc-js/tester.js +++ b/src/tools/rustdoc-js/tester.js @@ -539,6 +539,22 @@ async function loadSearchJS(doc_folder, resource_suffix) { }; } +/** + * Returns true if `fileName` looks like a proper rustdoc-js test file. + * + * Mirrors compiletest's `is_test` filtering so editor temp/autosave files + * (for example Emacs `.#foo.js`) are not treated as tests. + */ +function isTestFile(fileName) { + if (!fileName.endsWith(".js")) { + return false; + } + + // `.`, `#`, and `~` are common temp-file prefixes. + const invalidPrefixes = [".", "#", "~"]; + return !invalidPrefixes.some(prefix => fileName.startsWith(prefix)); +} + function showHelp() { console.log("rustdoc-js options:"); console.log(" --doc-folder [PATH] : location of the generated doc folder"); @@ -626,7 +642,7 @@ async function main(argv) { } } else if (opts["test_folder"].length !== 0) { for (const file of fs.readdirSync(opts["test_folder"])) { - if (!file.endsWith(".js")) { + if (!isTestFile(file)) { continue; } process.stdout.write(`Testing ${file} ... `); From af565829d7807db0b2b6ad629e2cbb325929a5cb Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 24 Jul 2026 12:55:35 +1000 Subject: [PATCH 45/61] Avoid `#[target_features]` The string `#[target_features]` is used in various error messages as well as the compiler's code. But there is no such attribute, which I found very confusing. I think it means "one or more target features", e.g. covering both `#[target_feature(foo)]` and `#[target_feature(foo, bar, baz)]`. We already use `#[target_feature(..)]` for that meaning in several places. So this commit changes all `#[target_features]` occurrences to `#[target_feature(..)]`. It also changes a few `#[target_feature]` (no plural) occurrences to `#[target_feature(..)]` for consistency with other mentions nearby in error messages. --- compiler/rustc_hir/src/hir.rs | 2 +- compiler/rustc_middle/src/ty/error.rs | 3 +- compiler/rustc_middle/src/ty/print/pretty.rs | 2 +- compiler/rustc_target/src/target_features.rs | 2 +- .../src/error_reporting/infer/mod.rs | 36 +++++++-------- .../error_reporting/infer/note_and_explain.rs | 4 +- .../traits/fulfillment_errors.rs | 2 +- .../fn-exception-target-features.stderr | 4 +- ...e-effective-target-features.default.stderr | 2 +- ...e-effective-target-features.feature.stderr | 2 +- .../rfc-2396-target_feature-11/fn-ptr.stderr | 16 +++---- .../rfc-2396-target_feature-11/fn-traits.rs | 8 ++-- .../fn-traits.stderr | 44 +++++++++---------- .../trait-impl.stderr | 2 +- .../target-feature/invalid-attribute.stderr | 2 +- 15 files changed, 66 insertions(+), 65 deletions(-) diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 2e272fa3af9d0..28200bc3f4712 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -4769,7 +4769,7 @@ pub enum RestrictionKind<'hir> { /// explicitly to allow unsafe operations. #[derive(Copy, Clone, Debug, StableHash, PartialEq, Eq)] pub enum HeaderSafety { - /// A safe function annotated with `#[target_features]`. + /// A safe function annotated with `#[target_feature(..)]`. /// The type system treats this function as an unsafe function, /// but safety checking will check this enum to treat it as safe /// and allowing calling other safe target feature functions with diff --git a/compiler/rustc_middle/src/ty/error.rs b/compiler/rustc_middle/src/ty/error.rs index 83670daf909fa..a16352e45564e 100644 --- a/compiler/rustc_middle/src/ty/error.rs +++ b/compiler/rustc_middle/src/ty/error.rs @@ -129,7 +129,8 @@ impl<'tcx> TypeError<'tcx> { } TypeError::IntrinsicCast => "cannot coerce intrinsics to function pointers".into(), TypeError::TargetFeatureCast(_) => { - "cannot coerce functions with `#[target_feature]` to safe function pointers".into() + "cannot coerce functions with `#[target_feature(..)]` to safe function pointers" + .into() } } } diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs index 3c7e3b63ee9b5..3614a67b71045 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -789,7 +789,7 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write { let mut sig = self.tcx().fn_sig(def_id).instantiate(self.tcx(), args).skip_norm_wip(); if self.tcx().codegen_fn_attrs(def_id).safe_target_features { - write!(self, "#[target_features] ")?; + write!(self, "#[target_feature(..)] ")?; sig = sig.map_bound(|mut sig| { sig.fn_sig_kind = sig.fn_sig_kind.set_safety(hir::Safety::Safe); sig diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs index fc25849b2d602..3e07621a20d55 100644 --- a/compiler/rustc_target/src/target_features.rs +++ b/compiler/rustc_target/src/target_features.rs @@ -1219,7 +1219,7 @@ impl Target { /// These features are checked against the target features reported by LLVM based on /// `-Ctarget-cpu` and `-Ctarget-features`. Constraint violations result in a warning. /// - /// We also check features enabled via `#[target_features]` (and here, constraint violations + /// We also check features enabled via `#[target_feature(..)]` (and here, constraint violations /// emit a hard error), including features enabled indirectly via implications -- but if LLVM /// considers more features to be implied than we do, that could bypass this check! pub fn abi_required_features(&self) -> FeatureConstraints { diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs index 69ef6c8d2a5c4..0af41423b846a 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs @@ -773,17 +773,17 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { let (lt1, sig1) = get_lifetimes(sig1); let (lt2, sig2) = get_lifetimes(sig2); - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T let mut values = (DiagStyledString::normal("".to_string()), DiagStyledString::normal("".to_string())); - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T - // ^^^^^^^^^^^^^^^^^^ + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // ^^^^^^^^^^^^^^^^^^^^^ let fn_item_prefix_and_safety = |fn_def, sig: ty::FnSig<'_>| match fn_def { None => ("", sig.safety().prefix_str()), Some((did, _)) => { if self.tcx.codegen_fn_attrs(did).safe_target_features { - ("#[target_features] ", "") + ("#[target_feature(..)] ", "") } else { ("", sig.safety().prefix_str()) } @@ -794,19 +794,19 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { values.0.push(prefix1, prefix1 != prefix2); values.1.push(prefix2, prefix1 != prefix2); - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T - // ^^^^^^^^ + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // ^^^^^^^^ let lifetime_diff = lt1 != lt2; values.0.push(lt1, lifetime_diff); values.1.push(lt2, lifetime_diff); - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T - // ^^^^^^ + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // ^^^^^^ values.0.push(safety1, safety1 != safety2); values.1.push(safety2, safety1 != safety2); - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T - // ^^^^^^^^^^ + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // ^^^^^^^^^^ if sig1.abi() != ExternAbi::Rust { values.0.push(format!("extern {} ", sig1.abi()), sig1.abi() != sig2.abi()); } @@ -814,13 +814,13 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { values.1.push(format!("extern {} ", sig2.abi()), sig1.abi() != sig2.abi()); } - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T - // ^^^ + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // ^^^ values.0.push_normal("fn("); values.1.push_normal("fn("); - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T - // ^^^^^ + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // ^^^^^ let len1 = sig1.inputs().len(); let len2 = sig2.inputs().len(); let splatted_arg_index1 = sig1.splatted().map(usize::from); @@ -868,13 +868,13 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { values.1.push("...", !sig1.c_variadic()); } - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T - // ^ + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // ^ values.0.push_normal(")"); values.1.push_normal(")"); - // #[target_features] for<'a> unsafe extern "C" fn(&'a T) -> &'a T - // ^^^^^^^^ + // #[target_feature(..)] for<'a> unsafe extern "C" fn(&'a T) -> &'a T + // ^^^^^^^^ let output1 = sig1.output(); let output2 = sig2.output(); let (x1, x2) = self.cmp(output1, output2); diff --git a/compiler/rustc_trait_selection/src/error_reporting/infer/note_and_explain.rs b/compiler/rustc_trait_selection/src/error_reporting/infer/note_and_explain.rs index f93d28c29cdfa..7fcc53c2b4348 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/infer/note_and_explain.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/infer/note_and_explain.rs @@ -621,9 +621,9 @@ impl Trait for X { TypeError::TargetFeatureCast(def_id) => { let target_spans = find_attr!(tcx, def_id, TargetFeature{attr_span: span, was_forced: false, ..} => *span); diag.note( - "functions with `#[target_feature]` can only be coerced to `unsafe` function pointers" + "functions with `#[target_feature(..)]` can only be coerced to `unsafe` function pointers" ); - diag.span_labels(target_spans, "`#[target_feature]` added here"); + diag.span_labels(target_spans, "`#[target_feature(..)]` added here"); } _ => {} } diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index b98c3fab5bcb5..8af061168a865 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -555,7 +555,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { }; if is_fn_trait && is_target_feature_fn { err.note( - "`#[target_feature]` functions do not implement the `Fn` traits", + "`#[target_feature(..)]` functions do not implement the `Fn` traits", ); err.note( "try casting the function to a `fn` pointer or wrapping it in a closure", diff --git a/tests/ui/async-await/async-closures/fn-exception-target-features.stderr b/tests/ui/async-await/async-closures/fn-exception-target-features.stderr index f0846bfdb1250..62b0c41f4ebc5 100644 --- a/tests/ui/async-await/async-closures/fn-exception-target-features.stderr +++ b/tests/ui/async-await/async-closures/fn-exception-target-features.stderr @@ -1,4 +1,4 @@ -error[E0277]: the trait bound `#[target_features] fn() -> Pin + 'static)>> {target_feature}: AsyncFn()` is not satisfied +error[E0277]: the trait bound `#[target_feature(..)] fn() -> Pin + 'static)>> {target_feature}: AsyncFn()` is not satisfied --> $DIR/fn-exception-target-features.rs:15:10 | LL | test(target_feature); @@ -6,7 +6,7 @@ LL | test(target_feature); | | | required by a bound introduced by this call | - = help: the trait `AsyncFn()` is not implemented for fn item `#[target_features] fn() -> Pin + 'static)>> {target_feature}` + = help: the trait `AsyncFn()` is not implemented for fn item `#[target_feature(..)] fn() -> Pin + 'static)>> {target_feature}` note: required by a bound in `test` --> $DIR/fn-exception-target-features.rs:12:17 | diff --git a/tests/ui/feature-gates/feature-gate-effective-target-features.default.stderr b/tests/ui/feature-gates/feature-gate-effective-target-features.default.stderr index e699c5d77b42c..e503e417f61cf 100644 --- a/tests/ui/feature-gates/feature-gate-effective-target-features.default.stderr +++ b/tests/ui/feature-gates/feature-gate-effective-target-features.default.stderr @@ -29,7 +29,7 @@ note: type in trait LL | fn foo(&self); | ^^^^^^^^^^^^^^ = note: expected signature `fn(&Bar2)` - found signature `#[target_features] fn(&Bar2)` + found signature `#[target_feature(..)] fn(&Bar2)` error: aborting due to 3 previous errors diff --git a/tests/ui/feature-gates/feature-gate-effective-target-features.feature.stderr b/tests/ui/feature-gates/feature-gate-effective-target-features.feature.stderr index bf9f5d73e0573..06090cbbef6fe 100644 --- a/tests/ui/feature-gates/feature-gate-effective-target-features.feature.stderr +++ b/tests/ui/feature-gates/feature-gate-effective-target-features.feature.stderr @@ -32,7 +32,7 @@ note: type in trait LL | fn foo(&self); | ^^^^^^^^^^^^^^ = note: expected signature `fn(&Bar2)` - found signature `#[target_features] fn(&Bar2)` + found signature `#[target_feature(..)] fn(&Bar2)` error: aborting due to 2 previous errors; 1 warning emitted diff --git a/tests/ui/rfcs/rfc-2396-target_feature-11/fn-ptr.stderr b/tests/ui/rfcs/rfc-2396-target_feature-11/fn-ptr.stderr index 5bfbc236bc4e3..47bfc68f43a45 100644 --- a/tests/ui/rfcs/rfc-2396-target_feature-11/fn-ptr.stderr +++ b/tests/ui/rfcs/rfc-2396-target_feature-11/fn-ptr.stderr @@ -2,31 +2,31 @@ error[E0308]: mismatched types --> $DIR/fn-ptr.rs:12:21 | LL | #[target_feature(enable = "avx")] - | --------------------------------- `#[target_feature]` added here + | --------------------------------- `#[target_feature(..)]` added here ... LL | let foo: fn() = foo_avx; - | ---- ^^^^^^^ cannot coerce functions with `#[target_feature]` to safe function pointers + | ---- ^^^^^^^ cannot coerce functions with `#[target_feature(..)]` to safe function pointers | | | expected due to this | = note: expected fn pointer `fn()` - found fn item `#[target_features] fn() {foo_avx}` - = note: functions with `#[target_feature]` can only be coerced to `unsafe` function pointers + found fn item `#[target_feature(..)] fn() {foo_avx}` + = note: functions with `#[target_feature(..)]` can only be coerced to `unsafe` function pointers error[E0308]: mismatched types --> $DIR/fn-ptr.rs:21:21 | LL | #[target_feature(enable = "sse2")] - | ---------------------------------- `#[target_feature]` added here + | ---------------------------------- `#[target_feature(..)]` added here ... LL | let foo: fn() = foo; - | ---- ^^^ cannot coerce functions with `#[target_feature]` to safe function pointers + | ---- ^^^ cannot coerce functions with `#[target_feature(..)]` to safe function pointers | | | expected due to this | = note: expected fn pointer `fn()` - found fn item `#[target_features] fn() {foo}` - = note: functions with `#[target_feature]` can only be coerced to `unsafe` function pointers + found fn item `#[target_feature(..)] fn() {foo}` + = note: functions with `#[target_feature(..)]` can only be coerced to `unsafe` function pointers error: aborting due to 2 previous errors diff --git a/tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.rs b/tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.rs index b7eaabdf6ad3f..778a3fb7bbbd7 100644 --- a/tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.rs +++ b/tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.rs @@ -26,10 +26,10 @@ fn call_once_i32(f: impl FnOnce(i32)) { } fn main() { - call(foo); //~ ERROR expected an `Fn()` closure, found `#[target_features] fn() {foo}` - call_mut(foo); //~ ERROR expected an `FnMut()` closure, found `#[target_features] fn() {foo}` - call_once(foo); //~ ERROR expected an `FnOnce()` closure, found `#[target_features] fn() {foo}` - call_once_i32(bar); //~ ERROR expected an `FnOnce(i32)` closure, found `#[target_features] fn(i32) {bar}` + call(foo); //~ ERROR expected an `Fn()` closure, found `#[target_feature(..)] fn() {foo}` + call_mut(foo); //~ ERROR expected an `FnMut()` closure, found `#[target_feature(..)] fn() {foo}` + call_once(foo); //~ ERROR expected an `FnOnce()` closure, found `#[target_feature(..)] fn() {foo}` + call_once_i32(bar); //~ ERROR expected an `FnOnce(i32)` closure, found `#[target_feature(..)] fn(i32) {bar}` call(foo_unsafe); //~^ ERROR expected an `Fn()` closure, found `unsafe fn() {foo_unsafe}` diff --git a/tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.stderr b/tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.stderr index 5333307a59280..43744636be196 100644 --- a/tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.stderr +++ b/tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.stderr @@ -1,14 +1,14 @@ -error[E0277]: expected an `Fn()` closure, found `#[target_features] fn() {foo}` +error[E0277]: expected an `Fn()` closure, found `#[target_feature(..)] fn() {foo}` --> $DIR/fn-traits.rs:29:10 | LL | call(foo); - | ---- ^^^ expected an `Fn()` closure, found `#[target_features] fn() {foo}` + | ---- ^^^ expected an `Fn()` closure, found `#[target_feature(..)] fn() {foo}` | | | required by a bound introduced by this call | - = help: the trait `Fn()` is not implemented for fn item `#[target_features] fn() {foo}` - = note: wrap the `#[target_features] fn() {foo}` in a closure with no arguments: `|| { /* code */ }` - = note: `#[target_feature]` functions do not implement the `Fn` traits + = help: the trait `Fn()` is not implemented for fn item `#[target_feature(..)] fn() {foo}` + = note: wrap the `#[target_feature(..)] fn() {foo}` in a closure with no arguments: `|| { /* code */ }` + = note: `#[target_feature(..)]` functions do not implement the `Fn` traits = note: try casting the function to a `fn` pointer or wrapping it in a closure note: required by a bound in `call` --> $DIR/fn-traits.rs:12:17 @@ -16,17 +16,17 @@ note: required by a bound in `call` LL | fn call(f: impl Fn()) { | ^^^^ required by this bound in `call` -error[E0277]: expected an `FnMut()` closure, found `#[target_features] fn() {foo}` +error[E0277]: expected an `FnMut()` closure, found `#[target_feature(..)] fn() {foo}` --> $DIR/fn-traits.rs:30:14 | LL | call_mut(foo); - | -------- ^^^ expected an `FnMut()` closure, found `#[target_features] fn() {foo}` + | -------- ^^^ expected an `FnMut()` closure, found `#[target_feature(..)] fn() {foo}` | | | required by a bound introduced by this call | - = help: the trait `FnMut()` is not implemented for fn item `#[target_features] fn() {foo}` - = note: wrap the `#[target_features] fn() {foo}` in a closure with no arguments: `|| { /* code */ }` - = note: `#[target_feature]` functions do not implement the `Fn` traits + = help: the trait `FnMut()` is not implemented for fn item `#[target_feature(..)] fn() {foo}` + = note: wrap the `#[target_feature(..)] fn() {foo}` in a closure with no arguments: `|| { /* code */ }` + = note: `#[target_feature(..)]` functions do not implement the `Fn` traits = note: try casting the function to a `fn` pointer or wrapping it in a closure note: required by a bound in `call_mut` --> $DIR/fn-traits.rs:16:25 @@ -34,17 +34,17 @@ note: required by a bound in `call_mut` LL | fn call_mut(mut f: impl FnMut()) { | ^^^^^^^ required by this bound in `call_mut` -error[E0277]: expected an `FnOnce()` closure, found `#[target_features] fn() {foo}` +error[E0277]: expected an `FnOnce()` closure, found `#[target_feature(..)] fn() {foo}` --> $DIR/fn-traits.rs:31:15 | LL | call_once(foo); - | --------- ^^^ expected an `FnOnce()` closure, found `#[target_features] fn() {foo}` + | --------- ^^^ expected an `FnOnce()` closure, found `#[target_feature(..)] fn() {foo}` | | | required by a bound introduced by this call | - = help: the trait `FnOnce()` is not implemented for fn item `#[target_features] fn() {foo}` - = note: wrap the `#[target_features] fn() {foo}` in a closure with no arguments: `|| { /* code */ }` - = note: `#[target_feature]` functions do not implement the `Fn` traits + = help: the trait `FnOnce()` is not implemented for fn item `#[target_feature(..)] fn() {foo}` + = note: wrap the `#[target_feature(..)] fn() {foo}` in a closure with no arguments: `|| { /* code */ }` + = note: `#[target_feature(..)]` functions do not implement the `Fn` traits = note: try casting the function to a `fn` pointer or wrapping it in a closure note: required by a bound in `call_once` --> $DIR/fn-traits.rs:20:22 @@ -52,16 +52,16 @@ note: required by a bound in `call_once` LL | fn call_once(f: impl FnOnce()) { | ^^^^^^^^ required by this bound in `call_once` -error[E0277]: expected an `FnOnce(i32)` closure, found `#[target_features] fn(i32) {bar}` +error[E0277]: expected an `FnOnce(i32)` closure, found `#[target_feature(..)] fn(i32) {bar}` --> $DIR/fn-traits.rs:32:19 | LL | call_once_i32(bar); - | ------------- ^^^ expected an `FnOnce(i32)` closure, found `#[target_features] fn(i32) {bar}` + | ------------- ^^^ expected an `FnOnce(i32)` closure, found `#[target_feature(..)] fn(i32) {bar}` | | | required by a bound introduced by this call | - = help: the trait `FnOnce(i32)` is not implemented for fn item `#[target_features] fn(i32) {bar}` - = note: `#[target_feature]` functions do not implement the `Fn` traits + = help: the trait `FnOnce(i32)` is not implemented for fn item `#[target_feature(..)] fn(i32) {bar}` + = note: `#[target_feature(..)]` functions do not implement the `Fn` traits = note: try casting the function to a `fn` pointer or wrapping it in a closure note: required by a bound in `call_once_i32` --> $DIR/fn-traits.rs:24:26 @@ -80,7 +80,7 @@ LL | call(foo_unsafe); = help: the trait `Fn()` is not implemented for fn item `unsafe fn() {foo_unsafe}` = note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ }` = note: unsafe function cannot be called generically without an unsafe block - = note: `#[target_feature]` functions do not implement the `Fn` traits + = note: `#[target_feature(..)]` functions do not implement the `Fn` traits = note: try casting the function to a `fn` pointer or wrapping it in a closure note: required by a bound in `call` --> $DIR/fn-traits.rs:12:17 @@ -99,7 +99,7 @@ LL | call_mut(foo_unsafe); = help: the trait `FnMut()` is not implemented for fn item `unsafe fn() {foo_unsafe}` = note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ }` = note: unsafe function cannot be called generically without an unsafe block - = note: `#[target_feature]` functions do not implement the `Fn` traits + = note: `#[target_feature(..)]` functions do not implement the `Fn` traits = note: try casting the function to a `fn` pointer or wrapping it in a closure note: required by a bound in `call_mut` --> $DIR/fn-traits.rs:16:25 @@ -118,7 +118,7 @@ LL | call_once(foo_unsafe); = help: the trait `FnOnce()` is not implemented for fn item `unsafe fn() {foo_unsafe}` = note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ }` = note: unsafe function cannot be called generically without an unsafe block - = note: `#[target_feature]` functions do not implement the `Fn` traits + = note: `#[target_feature(..)]` functions do not implement the `Fn` traits = note: try casting the function to a `fn` pointer or wrapping it in a closure note: required by a bound in `call_once` --> $DIR/fn-traits.rs:20:22 diff --git a/tests/ui/rfcs/rfc-2396-target_feature-11/trait-impl.stderr b/tests/ui/rfcs/rfc-2396-target_feature-11/trait-impl.stderr index f2ef2b2f3b86b..39b0e5ea18f7d 100644 --- a/tests/ui/rfcs/rfc-2396-target_feature-11/trait-impl.stderr +++ b/tests/ui/rfcs/rfc-2396-target_feature-11/trait-impl.stderr @@ -19,7 +19,7 @@ note: type in trait LL | fn foo(&self); | ^^^^^^^^^^^^^^ = note: expected signature `fn(&Bar)` - found signature `#[target_features] fn(&Bar)` + found signature `#[target_feature(..)] fn(&Bar)` error: `#[target_feature(..)]` cannot be applied to safe trait method --> $DIR/trait-impl.rs:21:5 diff --git a/tests/ui/target-feature/invalid-attribute.stderr b/tests/ui/target-feature/invalid-attribute.stderr index abe3ab431635f..6d45f32c7609c 100644 --- a/tests/ui/target-feature/invalid-attribute.stderr +++ b/tests/ui/target-feature/invalid-attribute.stderr @@ -206,7 +206,7 @@ note: type in trait LL | fn foo(); | ^^^^^^^^^ = note: expected signature `fn()` - found signature `#[target_features] fn()` + found signature `#[target_feature(..)] fn()` error: the feature named `+sse2` is not valid for this target --> $DIR/invalid-attribute.rs:117:18 From ef765baf7e445767e2cb0f6320147e589f5c1777 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Thu, 23 Jul 2026 21:28:31 -0700 Subject: [PATCH 46/61] rustdoc: use `to_ascii_lowercase` for HTML tags --- src/librustdoc/passes/lint/html_tags.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/librustdoc/passes/lint/html_tags.rs b/src/librustdoc/passes/lint/html_tags.rs index 3849be8537a49..4ceac338c8b9e 100644 --- a/src/librustdoc/passes/lint/html_tags.rs +++ b/src/librustdoc/passes/lint/html_tags.rs @@ -264,7 +264,7 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & ) { match tag { HtmlOrMarkdownTag::Html(tag, range) => { - if !is_implicitly_self_closing(&tag.to_lowercase()) { + if !is_implicitly_self_closing(&tag.to_ascii_lowercase()) { report_diag( format!("unclosed HTML tag `{tag}`"), range, @@ -453,9 +453,9 @@ impl TagParser { } fn drop_tag(&mut self, range: Range, f: &impl Fn(String, &Range, HtmlDiagMode)) { - let tag_name_low = self.tag_name.to_lowercase(); + let tag_name_low = self.tag_name.to_ascii_lowercase(); let tag_name_is_match = |tag: &HtmlOrMarkdownTag| match tag { - HtmlOrMarkdownTag::Html(name, _span) => name.to_lowercase() == tag_name_low, + HtmlOrMarkdownTag::Html(name, _span) => name.to_ascii_lowercase() == tag_name_low, HtmlOrMarkdownTag::Markdown(..) => false, }; if let Some(pos) = self.tags.iter().rposition(tag_name_is_match) { @@ -658,7 +658,7 @@ impl TagParser { let valid = ALLOWED_UNCLOSED.contains(&&self.tag_name[..]) || self.tags.iter().take(pos + 1).any(|tag| match tag { HtmlOrMarkdownTag::Html(at, _) => { - let at = at.to_lowercase(); + let at = at.to_ascii_lowercase(); at == "svg" || at == "math" } HtmlOrMarkdownTag::Markdown(..) => false, From ef33862446a20f1da02a6a556188613733658609 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Thu, 23 Jul 2026 22:51:18 -0700 Subject: [PATCH 47/61] rustdoc: helper method for generating doc comments --- compiler/rustc_ast/src/ast.rs | 9 +++++++++ src/librustdoc/passes/lint/html_tags.rs | 7 +------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index bd5ce5d18b839..33b2460908c28 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -3415,6 +3415,15 @@ pub enum AttrStyle { Inner, } +impl AttrStyle { + pub fn line_doc_comment_prefix(self) -> &'static str { + match self { + AttrStyle::Outer => "///", + AttrStyle::Inner => "//!", + } + } +} + /// A list of attributes. pub type AttrVec = ThinVec; diff --git a/src/librustdoc/passes/lint/html_tags.rs b/src/librustdoc/passes/lint/html_tags.rs index 4ceac338c8b9e..a2db26b782246 100644 --- a/src/librustdoc/passes/lint/html_tags.rs +++ b/src/librustdoc/passes/lint/html_tags.rs @@ -6,7 +6,6 @@ use std::ops::Range; use std::str::CharIndices; use itertools::Itertools as _; -use rustc_ast::AttrStyle; use rustc_ast::attr::AttributeExt; use rustc_ast::token::{CommentKind, DocFragmentKind}; use rustc_hir::HirId; @@ -171,14 +170,10 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & let Some(doc_attr_style) = style_iter.next() && style_iter.all(|style| style == doc_attr_style) { - let mark = match doc_attr_style { - AttrStyle::Outer => "/// ", - AttrStyle::Inner => "//! ", - }; lint.span_suggestion( html_tag_span, "to turn off Markdown parsing, put the tag at the start of the line", - format!("\n{mark}{doc}", doc=&dox[html_tag_range.clone()]), + format!("\n{mark} {doc}", mark=doc_attr_style.line_doc_comment_prefix(), doc=&dox[html_tag_range.clone()]), Applicability::MachineApplicable, ); } From 158fdbf013233744ec21c9de1169e5e61db8a971 Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 28 Apr 2026 12:08:43 +0200 Subject: [PATCH 48/61] when bailing on ambiguity, don't force other results to ambig --- .../src/solve/search_graph.rs | 9 +-- compiler/rustc_type_ir/src/interner.rs | 2 +- .../rustc_type_ir/src/search_graph/mod.rs | 55 ++++++++----------- .../global-where-bound-normalization.rs | 45 +++++++++++++++ 4 files changed, 71 insertions(+), 40 deletions(-) create mode 100644 tests/ui/traits/next-solver/global-where-bound-normalization.rs diff --git a/compiler/rustc_next_trait_solver/src/solve/search_graph.rs b/compiler/rustc_next_trait_solver/src/solve/search_graph.rs index 4918258350bf3..fb44782ffb039 100644 --- a/compiler/rustc_next_trait_solver/src/solve/search_graph.rs +++ b/compiler/rustc_next_trait_solver/src/solve/search_graph.rs @@ -107,6 +107,7 @@ where response_no_constraints(cx, input, Certainty::overflow(true)) } + const FIXPOINT_OVERFLOW_AMBIGUITY_KIND: Certainty = Certainty::overflow(false); fn fixpoint_overflow_result( cx: I, input: CanonicalInput, @@ -126,14 +127,6 @@ where }) } - fn propagate_ambiguity( - cx: I, - for_input: CanonicalInput, - certainty: Certainty, - ) -> (QueryResult, AccessedOpaques) { - response_no_constraints(cx, for_input, certainty) - } - fn compute_goal( search_graph: &mut SearchGraph, cx: I, diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index bfa1c982bd0b7..fd02f017b2950 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -680,7 +680,7 @@ impl CollectAndApply for Result { impl search_graph::Cx for I { type Input = CanonicalInput; type Result = (QueryResult, AccessedOpaques); - type AmbiguityInfo = Certainty; + type AmbiguityKind = Certainty; type DepNodeIndex = I::DepNodeIndex; type Tracked = I::Tracked; diff --git a/compiler/rustc_type_ir/src/search_graph/mod.rs b/compiler/rustc_type_ir/src/search_graph/mod.rs index 711e7ae1c8945..b1635e7e4097c 100644 --- a/compiler/rustc_type_ir/src/search_graph/mod.rs +++ b/compiler/rustc_type_ir/src/search_graph/mod.rs @@ -40,7 +40,7 @@ pub use global_cache::GlobalCache; pub trait Cx: Copy { type Input: Debug + Eq + Hash + Copy; type Result: Debug + Eq + Hash + Copy; - type AmbiguityInfo: Debug + Eq + Hash + Copy; + type AmbiguityKind: Debug + Eq + Hash + Copy; type DepNodeIndex; type Tracked: Debug; @@ -92,6 +92,8 @@ pub trait Delegate: Sized { cx: Self::Cx, input: ::Input, ) -> ::Result; + + const FIXPOINT_OVERFLOW_AMBIGUITY_KIND: ::AmbiguityKind; fn fixpoint_overflow_result( cx: Self::Cx, input: ::Input, @@ -99,12 +101,7 @@ pub trait Delegate: Sized { fn is_ambiguous_result( result: ::Result, - ) -> Option<::AmbiguityInfo>; - fn propagate_ambiguity( - cx: Self::Cx, - for_input: ::Input, - ambiguity_info: ::AmbiguityInfo, - ) -> ::Result; + ) -> Option<::AmbiguityKind>; fn compute_goal( search_graph: &mut SearchGraph, @@ -955,8 +952,7 @@ impl, X: Cx> SearchGraph { #[derive_where(Debug; X: Cx)] enum RebaseReason { NoCycleUsages, - Ambiguity(X::AmbiguityInfo), - Overflow, + Ambiguity(X::AmbiguityKind), /// We've actually reached a fixpoint. /// /// This either happens in the first evaluation step for the cycle head. @@ -987,10 +983,9 @@ impl, X: Cx> SearchGraph { /// cache entries to also be ambiguous. This causes some undesirable ambiguity for nested /// goals whose result doesn't actually depend on this cycle head, but that's acceptable /// to me. - #[instrument(level = "trace", skip(self, cx))] + #[instrument(level = "trace", skip(self))] fn rebase_provisional_cache_entries( &mut self, - cx: X, stack_entry: &StackEntry, rebase_reason: RebaseReason, ) { @@ -1065,18 +1060,22 @@ impl, X: Cx> SearchGraph { } // The provisional cache entry does depend on the provisional result - // of the popped cycle head. We need to mutate the result of our - // provisional cache entry in case we did not reach a fixpoint. + // of the popped cycle head. In case we didn't actually reach a fixpoint, + // we must not keep potentially incorrect provisional cache entries around. match rebase_reason { // If the cycle head does not actually depend on itself, then // the provisional result used by the provisional cache entry // is not actually equal to the final provisional result. We // need to discard the provisional cache entry in this case. RebaseReason::NoCycleUsages => return false, - RebaseReason::Ambiguity(info) => { - *result = D::propagate_ambiguity(cx, input, info); + // If we avoid rerunning a goal due to ambiguity, we only keep provisional + // results which depend on that cycle head if these are already ambiguous + // themselves. + RebaseReason::Ambiguity(kind) => { + if !D::is_ambiguous_result(*result).is_some_and(|k| k == kind) { + return false; + } } - RebaseReason::Overflow => *result = D::fixpoint_overflow_result(cx, input), RebaseReason::ReachedFixpoint(None) => {} RebaseReason::ReachedFixpoint(Some(path_kind)) => { if !popped_head.usages.is_single(path_kind) { @@ -1380,17 +1379,12 @@ impl, X: Cx> SearchGraph { // final result is equal to the initial response for that case. if let Ok(fixpoint) = self.reached_fixpoint(&stack_entry, usages, result) { self.rebase_provisional_cache_entries( - cx, &stack_entry, RebaseReason::ReachedFixpoint(fixpoint), ); return EvaluationResult::finalize(stack_entry, encountered_overflow, result); } else if usages.is_empty() { - self.rebase_provisional_cache_entries( - cx, - &stack_entry, - RebaseReason::NoCycleUsages, - ); + self.rebase_provisional_cache_entries(&stack_entry, RebaseReason::NoCycleUsages); return EvaluationResult::finalize(stack_entry, encountered_overflow, result); } @@ -1399,19 +1393,15 @@ impl, X: Cx> SearchGraph { // response in the next iteration in this case. These changes would // likely either be caused by incompleteness or can change the maybe // cause from ambiguity to overflow. Returning ambiguity always - // preserves soundness and completeness even if the goal is be known - // to succeed or fail. + // preserves soundness and completeness even if the goal could + // otherwise succeed or fail. // // This prevents exponential blowup affecting multiple major crates. // As we only get to this branch if we haven't yet reached a fixpoint, // we also taint all provisional cache entries which depend on the // current goal. - if let Some(info) = D::is_ambiguous_result(result) { - self.rebase_provisional_cache_entries( - cx, - &stack_entry, - RebaseReason::Ambiguity(info), - ); + if let Some(kind) = D::is_ambiguous_result(result) { + self.rebase_provisional_cache_entries(&stack_entry, RebaseReason::Ambiguity(kind)); return EvaluationResult::finalize(stack_entry, encountered_overflow, result); }; @@ -1421,7 +1411,10 @@ impl, X: Cx> SearchGraph { if i >= D::FIXPOINT_STEP_LIMIT { debug!("canonical cycle overflow"); let result = D::fixpoint_overflow_result(cx, input); - self.rebase_provisional_cache_entries(cx, &stack_entry, RebaseReason::Overflow); + self.rebase_provisional_cache_entries( + &stack_entry, + RebaseReason::Ambiguity(D::FIXPOINT_OVERFLOW_AMBIGUITY_KIND), + ); return EvaluationResult::finalize(stack_entry, encountered_overflow, result); } diff --git a/tests/ui/traits/next-solver/global-where-bound-normalization.rs b/tests/ui/traits/next-solver/global-where-bound-normalization.rs new file mode 100644 index 0000000000000..e57fbf378a0d2 --- /dev/null +++ b/tests/ui/traits/next-solver/global-where-bound-normalization.rs @@ -0,0 +1,45 @@ +//@ check-pass +//@ compile-flags: -Znext-solver + +// Regression test for https://github.com/rust-lang/trait-system-refactor-initiative/issues/257. + +#![feature(rustc_attrs)] +#![expect(internal_features)] +#![rustc_no_implicit_bounds] + +pub trait Bound {} +impl Bound for u8 {} + +pub trait Proj { + type Assoc; +} +impl Proj for U { + type Assoc = U; +} +impl Proj for MyField { + type Assoc = u8; +} + +// While wf-checking the global bounds of `fn foo`, elaborating this outlives predicate triggered a +// cycle in the search graph along a particular probe path, which was not an actual solution. +// That cycle then resulted in a forced false-positive ambiguity due to a performance hack in the +// search graph and then ended up floundering the root goal evaluation. +pub trait Field: Proj {} + +struct MyField; +impl Field for MyField {} + +trait IdReqField { + type This; +} +impl IdReqField for F { + type This = F; +} + +fn foo() +where + ::This: Field, +{ +} + +fn main() {} From f176d8bd2d601214c798c322be447a73de32ca15 Mon Sep 17 00:00:00 2001 From: Vastargazing Date: Fri, 24 Jul 2026 11:05:35 +0300 Subject: [PATCH 49/61] std::sync::poison: disable auto_cfg on PoisonError::new PoisonError::new is defined twice, once under #[cfg(panic = "unwind")] and once under its negation, so exactly one definition exists in any given build. rustdoc's auto_cfg only sees whichever cfg survived expansion and tags the method as available under that cfg only, even though it is callable in every configuration - it just panics under panic=abort instead of returning. Disable auto_cfg on both variants so the portability note stops implying the method doesn't exist elsewhere. The doc comment already explains the panic=abort behavior in prose, so the cfg doesn't need to leak into the generated badge. Add a minimal regression test. --- library/std/src/sync/poison.rs | 2 ++ .../doc-cfg/mutually-exclusive-cfg-item.rs | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/rustdoc-html/doc-cfg/mutually-exclusive-cfg-item.rs diff --git a/library/std/src/sync/poison.rs b/library/std/src/sync/poison.rs index ee7e5f8586700..3c32ec34dee5b 100644 --- a/library/std/src/sync/poison.rs +++ b/library/std/src/sync/poison.rs @@ -263,6 +263,7 @@ impl PoisonError { /// or [`RwLock::read`](crate::sync::RwLock::read). /// /// This method may panic if std was built with `panic="abort"`. + #[doc(auto_cfg = false)] #[cfg(panic = "unwind")] #[stable(feature = "sync_poison", since = "1.2.0")] pub fn new(data: T) -> PoisonError { @@ -275,6 +276,7 @@ impl PoisonError { /// or [`RwLock::read`](crate::sync::RwLock::read). /// /// This method may panic if std was built with `panic="abort"`. + #[doc(auto_cfg = false)] #[cfg(not(panic = "unwind"))] #[stable(feature = "sync_poison", since = "1.2.0")] #[track_caller] diff --git a/tests/rustdoc-html/doc-cfg/mutually-exclusive-cfg-item.rs b/tests/rustdoc-html/doc-cfg/mutually-exclusive-cfg-item.rs new file mode 100644 index 0000000000000..d168bcdf0b41a --- /dev/null +++ b/tests/rustdoc-html/doc-cfg/mutually-exclusive-cfg-item.rs @@ -0,0 +1,24 @@ +// An item split into mutually-exclusive `#[cfg(..)]` variants that together cover +// every configuration must not get a portability note when `auto_cfg` is disabled. +// Regression test for . + +#![feature(doc_cfg)] +#![crate_name = "foo"] + +pub struct S; + +impl S { + //@ has 'foo/struct.S.html' + //@ count - '//*[@id="method.new"]/..//*[@class="stab portability"]' 0 + #[doc(auto_cfg = false)] + #[cfg(panic = "unwind")] + pub fn new() -> S { + S + } + + #[doc(auto_cfg = false)] + #[cfg(not(panic = "unwind"))] + pub fn new() -> S { + S + } +} From f8711a10fd8d25ea534d121745e023a7c886a54e Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Wed, 22 Jul 2026 22:12:06 +0200 Subject: [PATCH 50/61] reuse regular exported_non_generic_symbols logic in Miri --- compiler/rustc_codegen_ssa/src/back/mod.rs | 2 + .../src/back/symbol_export.rs | 12 ++++ compiler/rustc_passes/src/reachable.rs | 2 + src/tools/miri/src/bin/miri.rs | 66 +++---------------- src/tools/miri/src/helpers.rs | 52 ++++++++------- src/tools/miri/src/shims/foreign_items.rs | 6 +- 6 files changed, 55 insertions(+), 85 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/mod.rs b/compiler/rustc_codegen_ssa/src/back/mod.rs index 0f5050a9d7a9a..68db2d0cbf0bb 100644 --- a/compiler/rustc_codegen_ssa/src/back/mod.rs +++ b/compiler/rustc_codegen_ssa/src/back/mod.rs @@ -15,6 +15,8 @@ mod symbol_edit; pub mod symbol_export; pub mod write; +pub use symbol_export::{exported_non_generic_symbols_helper, reachable_non_generics_helper}; + /// The target triple depends on the deployment target, and is required to /// enable features such as cross-language LTO, and for picking the right /// Mach-O commands. diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs index 42e2b646e4793..51413d3a67a68 100644 --- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs +++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs @@ -54,6 +54,11 @@ fn reachable_non_generics_provider(tcx: TyCtxt<'_>, _: LocalCrate) -> DefIdMap) -> DefIdMap { let is_compiler_builtins = tcx.is_compiler_builtins(LOCAL_CRATE); let mut reachable_non_generics: DefIdMap<_> = tcx @@ -176,6 +181,13 @@ fn exported_non_generic_symbols_provider_local<'tcx>( return &[]; } + exported_non_generic_symbols_helper(tcx) +} + +/// Exposed separately *without* the "should codegen" check so Miri can access it. +pub fn exported_non_generic_symbols_helper<'tcx>( + tcx: TyCtxt<'tcx>, +) -> &'tcx [(ExportedSymbol<'tcx>, SymbolExportInfo)] { // FIXME: Sorting this is unnecessary since we are sorting later anyway. // Can we skip the later sorting? let sorted = tcx.with_stable_hashing_context(|mut hcx| { diff --git a/compiler/rustc_passes/src/reachable.rs b/compiler/rustc_passes/src/reachable.rs index fdae03ec49d9f..b8497aefb7767 100644 --- a/compiler/rustc_passes/src/reachable.rs +++ b/compiler/rustc_passes/src/reachable.rs @@ -444,6 +444,8 @@ fn has_custom_linkage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { // FIXME(nbdd0121): `#[used]` are marked as reachable here so it's picked up by // `linked_symbols` in cg_ssa. They won't be exported in binary or cdylib due to their // `SymbolExportLevel::Rust` export level but may end up being exported in dylibs. + // Also note that Miri is relying on this to be able to find private `link_section` statics + // across all crates. || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER) || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) // Right now, the only way to get "foreign item symbol aliases" is by being an EII-implementation. diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs index 372829ea07a62..641d37e16f1b9 100644 --- a/src/tools/miri/src/bin/miri.rs +++ b/src/tools/miri/src/bin/miri.rs @@ -11,7 +11,6 @@ extern crate rustc_codegen_ssa; extern crate rustc_data_structures; extern crate rustc_driver; -extern crate rustc_hir; extern crate rustc_interface; extern crate rustc_log; extern crate rustc_middle; @@ -48,14 +47,9 @@ use miri::{ use rustc_codegen_ssa::traits::CodegenBackend; use rustc_data_structures::sync::{self, DynSync}; use rustc_driver::Compilation; -use rustc_hir::{self as hir, Node}; use rustc_interface::interface::Config; use rustc_interface::util::DummyCodegenBackend; use rustc_log::tracing::debug; -use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; -use rustc_middle::middle::exported_symbols::{ - ExportedSymbol, SymbolExportInfo, SymbolExportKind, SymbolExportLevel, -}; use rustc_middle::query::LocalCrate; use rustc_middle::ty::TyCtxt; use rustc_session::config::{CrateType, ErrorOutputType, OptLevel}; @@ -258,58 +252,14 @@ impl rustc_driver::Callbacks for MiriDepCompilerCalls { // Queries overridden here affect the data stored in `rmeta` files of dependencies, // which will be used later in non-`MIRI_BE_RUSTC` mode. config.override_queries = Some(|_, local_providers| { - // We need to add #[used] symbols to exported_symbols for `lookup_link_section`. - // FIXME handle this somehow in rustc itself to avoid this hack. - local_providers.queries.exported_non_generic_symbols = |tcx, LocalCrate| { - let reachable_set = tcx.with_stable_hashing_context(|mut hcx| { - tcx.reachable_set(()).to_sorted(&mut hcx, true) - }); - tcx.arena.alloc_from_iter( - // This is based on: - // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L62-L63 - // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L174 - reachable_set.into_iter().filter_map(|&local_def_id| { - // Do the same filtering that rustc does: - // https://github.com/rust-lang/rust/blob/2962e7c0089d5c136f4e9600b7abccfbbde4973d/compiler/rustc_codegen_ssa/src/back/symbol_export.rs#L84-L102 - // Otherwise it may cause unexpected behaviours and ICEs - // (https://github.com/rust-lang/rust/issues/86261). - let is_reachable_non_generic = matches!( - tcx.hir_node_by_def_id(local_def_id), - Node::Item(&hir::Item { - kind: hir::ItemKind::Static(..) | hir::ItemKind::Fn{ .. }, - .. - }) | Node::ImplItem(&hir::ImplItem { - kind: hir::ImplItemKind::Fn(..), - .. - }) - if !tcx.generics_of(local_def_id).requires_monomorphization(tcx) - ); - if !is_reachable_non_generic { - return None; - } - let codegen_fn_attrs = tcx.codegen_fn_attrs(local_def_id); - if codegen_fn_attrs.contains_extern_indicator() - || codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER) - || codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) - { - Some(( - ExportedSymbol::NonGeneric(local_def_id.to_def_id()), - // Some dummy `SymbolExportInfo` here. We only use - // `exported_symbols` in shims/foreign_items.rs and the export info - // is ignored. - SymbolExportInfo { - level: SymbolExportLevel::C, - kind: SymbolExportKind::Text, - used: false, - rustc_std_internal_symbol: false, - }, - )) - } else { - None - } - }), - ) - } + // `exported_non_generic_symbols` is usually empty because we don't codegen anything. + // However, we need it for `lookup_link_section`. + // So overwrite the query with a version that dooes something even without codegen. + local_providers.queries.exported_non_generic_symbols = + |tcx, LocalCrate| rustc_codegen_ssa::back::exported_non_generic_symbols_helper(tcx); + // `exported_non_generic_symbols_helper` calls `reachable_non_generics`. + local_providers.queries.reachable_non_generics = + |tcx, LocalCrate| rustc_codegen_ssa::back::reachable_non_generics_helper(tcx); }); // Register our custom extra symbols. diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index c05c067d38b9d..8dc6b5f07b92e 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -116,34 +116,36 @@ pub fn path_ty_layout<'tcx>(cx: &impl LayoutOf<'tcx>, path: &[&str]) -> TyAndLay /// Call `f` for each exported symbol. pub fn iter_exported_symbols<'tcx>( tcx: TyCtxt<'tcx>, - mut f: impl FnMut(CrateNum, DefId) -> InterpResult<'tcx>, + mut f: impl FnMut(CrateNum, DefId, /* used */ bool) -> InterpResult<'tcx>, ) -> InterpResult<'tcx> { - // First, the symbols in the local crate. We can't use `exported_symbols` here as that - // skips `#[used]` statics (since `reachable_set` skips them in binary crates). - // So we walk all HIR items ourselves instead. + // First, the symbols in the local crate. We can't use `exported_symbols` here as that skips + // `#[used]` statics (since `reachable_set` does not specifically include them in binary crates, + // only in library crates). So we walk all HIR items ourselves instead. let crate_items = tcx.hir_crate_items(()); for def_id in crate_items.definitions() { - let exported = tcx.def_kind(def_id).has_codegen_attrs() && { - let codegen_attrs = tcx.codegen_fn_attrs(def_id); - codegen_attrs.contains_extern_indicator() - || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER) - || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) - }; + if !tcx.def_kind(def_id).has_codegen_attrs() || tcx.is_foreign_item(def_id) { + continue; + } + let codegen_attrs = tcx.codegen_fn_attrs(def_id); + let used = codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_COMPILER) + || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER); + if !(used || codegen_attrs.contains_extern_indicator()) { + continue; + } // FIXME: `#[no_mangle]` makes no sense on a generic item, but still causes it to be // considered "extern". Remove this once `no_mangle_generic_items` is a hard error. - let exported_mono = exported && { + let mono = { let generics = tcx.generics_of(def_id); !generics.requires_monomorphization(tcx) }; - if exported_mono { - f(LOCAL_CRATE, def_id.into())?; + if mono { + f(LOCAL_CRATE, def_id.into(), used)?; } } // Next, all our dependencies. - // `dependency_formats` includes all the transitive information needed to link a crate, - // which is what we need here since we need to dig out `exported_symbols` from all transitive - // dependencies. + // `dependency_formats` includes all the transitive information needed to link a crate, which is + // what we need to dig out `exported_symbols` from all transitive dependencies. let dependency_formats = tcx.dependency_formats(()); // Find the dependencies of the executable we are running. let dependency_format = dependency_formats @@ -157,11 +159,12 @@ pub fn iter_exported_symbols<'tcx>( continue; // Already handled above } - // We can ignore `_export_info` here: we are a Rust crate, and everything is exported - // from a Rust crate. - for &(symbol, _export_info) in tcx.exported_non_generic_symbols(cnum) { - if let ExportedSymbol::NonGeneric(def_id) = symbol { - f(cnum, def_id)?; + for &(symbol, export_info) in tcx.exported_non_generic_symbols(cnum) { + if let ExportedSymbol::NonGeneric(def_id) = symbol + // Sometimes Rust has to re-export FFI imports; skip those. + && !tcx.is_foreign_item(def_id) + { + f(cnum, def_id, export_info.used)?; } } } @@ -961,8 +964,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let mut array = vec![]; - iter_exported_symbols(tcx, |_cnum, def_id| { + iter_exported_symbols(tcx, |_cnum, def_id, used| { let attrs = tcx.codegen_fn_attrs(def_id); + if !used { + // We don't know if the symbol is actually going to be in the final binary, + // so we conservatively skip it. + return interp_ok(()); + } let Some(link_section) = attrs.link_section else { return interp_ok(()); }; diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index efe597a5c1f7d..683e9095f9b0c 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -132,12 +132,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { is_weak: bool, } let mut symbol_target: Option> = None; - helpers::iter_exported_symbols(tcx, |cnum, def_id| { + helpers::iter_exported_symbols(tcx, |cnum, def_id, _used| { let attrs = tcx.codegen_fn_attrs(def_id); - // Skip over imports of items. - if tcx.is_foreign_item(def_id) { - return interp_ok(()); - } // Skip over items without an explicitly defined symbol name. if !(attrs.symbol_name.is_some() || attrs.flags.contains(CodegenFnAttrFlags::NO_MANGLE) From 2dc6470711321dbc67d31aef0fa3c30743d3e012 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 23 Jul 2026 20:29:02 +0200 Subject: [PATCH 51/61] add `FnHeader::span` --- compiler/rustc_ast/src/ast.rs | 46 +++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index d496c397395e9..9d5ab18c80ac3 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -2339,27 +2339,7 @@ pub struct FnSig { impl FnSig { /// Return a span encompassing the header, or where to insert it if empty. pub fn header_span(&self) -> Span { - match self.header.ext { - Extern::Implicit(span) | Extern::Explicit(_, span) => { - return self.span.with_hi(span.hi()); - } - Extern::None => {} - } - - match self.header.safety { - Safety::Unsafe(span) | Safety::Safe(span) => return self.span.with_hi(span.hi()), - Safety::Default => {} - }; - - if let Some(coroutine_kind) = self.header.coroutine_kind { - return self.span.with_hi(coroutine_kind.span().hi()); - } - - if let Const::Yes(span) = self.header.constness { - return self.span.with_hi(span.hi()); - } - - self.span.shrink_to_lo() + self.header.span().unwrap_or(self.span.shrink_to_lo()) } /// The span of the header's safety, or where to insert it if empty. @@ -3836,6 +3816,30 @@ impl FnHeader { || matches!(constness, Const::Yes(_)) || !matches!(ext, Extern::None) } + + pub fn span(&self) -> Option { + let mut spans = smallvec::SmallVec::<[Span; 4]>::new(); + + match self.ext { + Extern::Implicit(span) | Extern::Explicit(_, span) => spans.push(span), + Extern::None => {} + } + + match self.safety { + Safety::Unsafe(span) | Safety::Safe(span) => spans.push(span), + Safety::Default => {} + }; + + if let Some(coroutine_kind) = self.coroutine_kind { + spans.push(coroutine_kind.span()); + } + + if let Const::Yes(span) = self.constness { + spans.push(span) + } + + spans.into_iter().reduce(Span::to) + } } impl Default for FnHeader { From b16960fa934618b2810145ec8ca0c0148bc5ce9c Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 23 Jul 2026 20:38:16 +0200 Subject: [PATCH 52/61] make `ident` optional in `check_extern_fn_signature` --- .../rustc_ast_passes/src/ast_validation.rs | 31 ++++++++++++++----- compiler/rustc_ast_passes/src/diagnostics.rs | 4 +-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index fe1da820cf74f..827f25a89d274 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -546,7 +546,13 @@ impl<'a> AstValidator<'a> { } /// Check that the signature of this function does not violate the constraints of its ABI. - fn check_extern_fn_signature(&self, abi: ExternAbi, ctxt: FnCtxt, ident: &Ident, sig: &FnSig) { + fn check_extern_fn_signature( + &self, + abi: ExternAbi, + ctxt: FnCtxt, + opt_function_name: Option<&Ident>, // None for function pointers + sig: &FnSig, + ) { match AbiMap::from_target(&self.sess.target).canonize_abi(abi, false) { AbiMapping::Direct(canon_abi) | AbiMapping::Deprecated(canon_abi) => { match canon_abi { @@ -575,7 +581,7 @@ impl<'a> AstValidator<'a> { self.reject_coroutine(abi, sig); // An `extern "custom"` function must have type `fn()`. - self.reject_params_or_return(abi, ident, sig); + self.reject_params_or_return(abi, opt_function_name, sig); } CanonAbi::Interrupt(interrupt_kind) => { @@ -600,7 +606,7 @@ impl<'a> AstValidator<'a> { self.reject_return(abi, sig); } else { // An `extern "interrupt"` function must have type `fn()`. - self.reject_params_or_return(abi, ident, sig); + self.reject_params_or_return(abi, opt_function_name, sig); } } } @@ -664,7 +670,12 @@ impl<'a> AstValidator<'a> { } } - fn reject_params_or_return(&self, abi: ExternAbi, ident: &Ident, sig: &FnSig) { + fn reject_params_or_return( + &self, + abi: ExternAbi, + opt_function_name: Option<&Ident>, // None for function pointers + sig: &FnSig, + ) { let mut spans: Vec<_> = sig.decl.inputs.iter().map(|p| p.span).collect(); if let FnRetTy::Ty(ref ret_ty) = sig.decl.output && match &ret_ty.kind { @@ -683,10 +694,14 @@ impl<'a> AstValidator<'a> { self.dcx().emit_err(diagnostics::AbiMustNotHaveParametersOrReturnType { spans, - symbol: ident.name, + abi, + suggestion_span, padding, - abi, + symbol: match opt_function_name { + Some(ident) => format!(" {}", ident.name), + None => String::new(), + }, }); } } @@ -1620,7 +1635,7 @@ impl Visitor<'_> for AstValidator<'_> { self.check_extern_fn_signature( self.extern_mod_abi.unwrap_or(ExternAbi::FALLBACK), FnCtxt::Foreign, - ident, + Some(ident), sig, ); @@ -1826,7 +1841,7 @@ impl Visitor<'_> for AstValidator<'_> { if let Some((extern_abi, extern_abi_span)) = ext { // Some ABIs impose special restrictions on the signature. - self.check_extern_fn_signature(extern_abi, ctxt, &fun.ident, &fun.sig); + self.check_extern_fn_signature(extern_abi, ctxt, Some(&fun.ident), &fun.sig); // #[track_caller] can only be used with the rust ABI. if let Some(attr) = attr::find_by_name(attrs, sym::track_caller) diff --git a/compiler/rustc_ast_passes/src/diagnostics.rs b/compiler/rustc_ast_passes/src/diagnostics.rs index 0cf7ef5c1de19..670cb4b66adff 100644 --- a/compiler/rustc_ast_passes/src/diagnostics.rs +++ b/compiler/rustc_ast_passes/src/diagnostics.rs @@ -1104,11 +1104,11 @@ pub(crate) struct AbiMustNotHaveParametersOrReturnType { #[suggestion( "remove the parameters and return type", applicability = "maybe-incorrect", - code = "{padding}fn {symbol}()", + code = "{padding}fn{symbol}()", style = "verbose" )] pub suggestion_span: Span, - pub symbol: Symbol, + pub symbol: String, pub padding: &'static str, } From 180f48d2cbb2e807aeacd29a69f33d8fb2b32bac Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 23 Jul 2026 21:38:49 +0200 Subject: [PATCH 53/61] add `BorrowedFnSig` to prevent a clone down the road --- compiler/rustc_ast/src/ast.rs | 13 ++++++++++++ .../rustc_ast_passes/src/ast_validation.rs | 21 ++++++++++++------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 9d5ab18c80ac3..927b081776b1b 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -2362,6 +2362,19 @@ impl FnSig { pub fn extern_span(&self) -> Span { self.header.ext.span().unwrap_or(self.safety_span().shrink_to_hi()) } + + pub fn as_borrowed(&self) -> BorrowedFnSig<'_> { + BorrowedFnSig { header: self.header, decl: &self.decl, span: self.span } + } +} + +/// A borrowed version of `FnSig`, used to share logic between function declarations and function +/// pointer types. +#[derive(Clone, Debug)] +pub struct BorrowedFnSig<'a> { + pub header: FnHeader, + pub decl: &'a FnDecl, + pub span: Span, } /// A constraint on an associated item. diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 827f25a89d274..c41563c08483a 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -551,7 +551,7 @@ impl<'a> AstValidator<'a> { abi: ExternAbi, ctxt: FnCtxt, opt_function_name: Option<&Ident>, // None for function pointers - sig: &FnSig, + sig: &BorrowedFnSig<'_>, ) { match AbiMap::from_target(&self.sess.target).canonize_abi(abi, false) { AbiMapping::Direct(canon_abi) | AbiMapping::Deprecated(canon_abi) => { @@ -615,7 +615,7 @@ impl<'a> AstValidator<'a> { } } - fn reject_safe_fn(&self, abi: ExternAbi, ctxt: FnCtxt, sig: &FnSig) { + fn reject_safe_fn(&self, abi: ExternAbi, ctxt: FnCtxt, sig: &BorrowedFnSig<'_>) { let dcx = self.dcx(); match sig.header.safety { @@ -641,7 +641,7 @@ impl<'a> AstValidator<'a> { } } - fn reject_coroutine(&self, abi: ExternAbi, sig: &FnSig) { + fn reject_coroutine(&self, abi: ExternAbi, sig: &BorrowedFnSig<'_>) { if let Some(coroutine_kind) = sig.header.coroutine_kind { let coroutine_kind_span = self .sess @@ -658,7 +658,7 @@ impl<'a> AstValidator<'a> { } } - fn reject_return(&self, abi: ExternAbi, sig: &FnSig) { + fn reject_return(&self, abi: ExternAbi, sig: &BorrowedFnSig<'_>) { if let FnRetTy::Ty(ref ret_ty) = sig.decl.output && match &ret_ty.kind { TyKind::Never => false, @@ -674,7 +674,7 @@ impl<'a> AstValidator<'a> { &self, abi: ExternAbi, opt_function_name: Option<&Ident>, // None for function pointers - sig: &FnSig, + sig: &BorrowedFnSig<'_>, ) { let mut spans: Vec<_> = sig.decl.inputs.iter().map(|p| p.span).collect(); if let FnRetTy::Ty(ref ret_ty) = sig.decl.output @@ -688,7 +688,7 @@ impl<'a> AstValidator<'a> { } if !spans.is_empty() { - let header_span = sig.header_span(); + let header_span = sig.header.span().unwrap_or(sig.span.shrink_to_lo()); let suggestion_span = header_span.shrink_to_hi().to(sig.decl.output.span()); let padding = if header_span.is_empty() { "" } else { " " }; @@ -1636,7 +1636,7 @@ impl Visitor<'_> for AstValidator<'_> { self.extern_mod_abi.unwrap_or(ExternAbi::FALLBACK), FnCtxt::Foreign, Some(ident), - sig, + &sig.as_borrowed(), ); if let Some(attr) = attr::find_by_name(fi.attrs(), sym::track_caller) @@ -1841,7 +1841,12 @@ impl Visitor<'_> for AstValidator<'_> { if let Some((extern_abi, extern_abi_span)) = ext { // Some ABIs impose special restrictions on the signature. - self.check_extern_fn_signature(extern_abi, ctxt, Some(&fun.ident), &fun.sig); + self.check_extern_fn_signature( + extern_abi, + ctxt, + Some(&fun.ident), + &fun.sig.as_borrowed(), + ); // #[track_caller] can only be used with the rust ABI. if let Some(attr) = attr::find_by_name(attrs, sym::track_caller) From f7a98cbff1b6b46785bf3a7d55c648a72d6928a8 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 23 Jul 2026 20:39:31 +0200 Subject: [PATCH 54/61] add `FnPtrTy::header` --- compiler/rustc_ast/src/ast.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 927b081776b1b..30b973728b8f9 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -2480,6 +2480,12 @@ pub struct FnPtrTy { pub decl_span: Span, } +impl FnPtrTy { + pub fn header(&self) -> FnHeader { + FnHeader { constness: Const::No, coroutine_kind: None, safety: self.safety, ext: self.ext } + } +} + #[derive(Clone, Encodable, Decodable, Debug, Walkable)] pub struct UnsafeBinderTy { pub generic_params: ThinVec, From 152ac5846ab38128bb34c3fd9f7e66964563c2f8 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 23 Jul 2026 20:45:27 +0200 Subject: [PATCH 55/61] run ABI checks on function pointer types and update the tests as needed. --- compiler/rustc_ast/src/ast.rs | 4 + .../rustc_ast_passes/src/ast_validation.rs | 18 +++++ tests/ui/abi/bad-custom.rs | 5 +- tests/ui/abi/bad-custom.stderr | 79 ++++++++++++++----- tests/ui/abi/cannot-be-called.amdgpu.stderr | 2 +- tests/ui/abi/cannot-be-called.avr.stderr | 2 +- tests/ui/abi/cannot-be-called.i686.stderr | 8 +- tests/ui/abi/cannot-be-called.msp430.stderr | 2 +- tests/ui/abi/cannot-be-called.nvptx.stderr | 2 +- tests/ui/abi/cannot-be-called.riscv32.stderr | 2 +- tests/ui/abi/cannot-be-called.riscv64.stderr | 2 +- tests/ui/abi/cannot-be-called.rs | 4 +- tests/ui/abi/cannot-be-called.x64.stderr | 8 +- tests/ui/abi/cannot-be-called.x64_win.stderr | 8 +- .../interrupt-invalid-signature.avr.stderr | 17 +++- .../interrupt-invalid-signature.i686.stderr | 52 ++++++++++-- .../interrupt-invalid-signature.msp430.stderr | 17 +++- ...interrupt-invalid-signature.riscv32.stderr | 32 +++++++- ...interrupt-invalid-signature.riscv64.stderr | 32 +++++++- tests/ui/abi/interrupt-invalid-signature.rs | 47 ++++++----- .../interrupt-invalid-signature.x64.stderr | 52 ++++++++++-- .../ui/abi/interrupt-returns-never-or-unit.rs | 33 ++++---- tests/ui/abi/unsupported.rs | 2 +- tests/ui/abi/unsupported.wasm32.stderr | 6 +- tests/ui/abi/unsupported.wasm64.stderr | 6 +- .../feature-gates/feature-gate-abi-custom.rs | 2 +- .../feature-gate-abi-custom.stderr | 6 +- .../feature-gate-abi-x86-interrupt.rs | 2 +- .../feature-gate-abi-x86-interrupt.stderr | 2 +- 29 files changed, 343 insertions(+), 111 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 30b973728b8f9..219e4bd00341b 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -2484,6 +2484,10 @@ impl FnPtrTy { pub fn header(&self) -> FnHeader { FnHeader { constness: Const::No, coroutine_kind: None, safety: self.safety, ext: self.ext } } + + pub fn as_borrowed_fn_sig<'a>(&'a self) -> BorrowedFnSig<'a> { + BorrowedFnSig { header: self.header(), decl: &self.decl, span: self.decl_span } + } } #[derive(Clone, Encodable, Decodable, Debug, Walkable)] diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index c41563c08483a..047212ac9d4ed 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -1153,6 +1153,24 @@ impl<'a> AstValidator<'a> { if let Extern::Implicit(extern_span) = bfty.ext { self.handle_missing_abi(extern_span, ty.id); } + + let ext = match bfty.ext { + Extern::None => None, + Extern::Implicit(_) => Some(ExternAbi::FALLBACK), + Extern::Explicit(str_lit, _) => { + ExternAbi::from_str(str_lit.symbol.as_str()).ok() + } + }; + + // Some ABIs impose special restrictions on the signature. + if let Some(extern_abi) = ext { + self.check_extern_fn_signature( + extern_abi, + FnCtxt::Free, + None, + &bfty.as_borrowed_fn_sig(), + ); + } } TyKind::TraitObject(bounds, ..) => { let mut any_lifetime_bounds = false; diff --git a/tests/ui/abi/bad-custom.rs b/tests/ui/abi/bad-custom.rs index 10e34fa19648a..cf026f7ae6536 100644 --- a/tests/ui/abi/bad-custom.rs +++ b/tests/ui/abi/bad-custom.rs @@ -72,16 +72,19 @@ unsafe extern "custom" { } fn caller(f: unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { + //~^ ERROR invalid signature for `extern "custom"` function unsafe { f(x) } //~^ ERROR functions with the "custom" ABI cannot be called } fn caller_by_ref(f: &unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { + //~^ ERROR invalid signature for `extern "custom"` function unsafe { f(x) } //~^ ERROR functions with the "custom" ABI cannot be called } type Custom = unsafe extern "custom" fn(i64) -> i64; +//~^ ERROR invalid signature for `extern "custom"` function fn caller_alias(f: Custom, mut x: i64) -> i64 { unsafe { f(x) } @@ -99,7 +102,7 @@ async unsafe extern "custom" fn no_async_fn() { //~| ERROR functions with the "custom" ABI cannot be `async` } -fn no_promotion_to_fn_trait(f: unsafe extern "custom" fn()) -> impl Fn() { +fn no_promotion_to_fn_trait(f: unsafe extern "custom" fn()) -> impl Fn() { //~^ ERROR expected an `Fn()` closure, found `unsafe extern "custom" fn()` f } diff --git a/tests/ui/abi/bad-custom.stderr b/tests/ui/abi/bad-custom.stderr index 46da91d80c993..9da50e93bd5ed 100644 --- a/tests/ui/abi/bad-custom.stderr +++ b/tests/ui/abi/bad-custom.stderr @@ -160,8 +160,47 @@ LL - safe fn extern_cannot_be_safe(); LL + fn extern_cannot_be_safe(); | +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:74:40 + | +LL | fn caller(f: unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { + | ^^^ ^^^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - fn caller(f: unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { +LL + fn caller(f: unsafe extern "custom" fn(), mut x: i64) -> i64 { + | + +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:80:48 + | +LL | fn caller_by_ref(f: &unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { + | ^^^ ^^^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - fn caller_by_ref(f: &unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { +LL + fn caller_by_ref(f: &unsafe extern "custom" fn(), mut x: i64) -> i64 { + | + +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:86:41 + | +LL | type Custom = unsafe extern "custom" fn(i64) -> i64; + | ^^^ ^^^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - type Custom = unsafe extern "custom" fn(i64) -> i64; +LL + type Custom = unsafe extern "custom" fn(); + | + error: functions with the "custom" ABI cannot be `async` - --> $DIR/bad-custom.rs:97:1 + --> $DIR/bad-custom.rs:100:1 | LL | async unsafe extern "custom" fn no_async_fn() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -173,7 +212,7 @@ LL + unsafe extern "custom" fn no_async_fn() { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:97:1 + --> $DIR/bad-custom.rs:100:1 | LL | async unsafe extern "custom" fn no_async_fn() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -185,9 +224,9 @@ LL | async unsafe extern "custom" fn no_async_fn() { | error[E0277]: expected an `Fn()` closure, found `unsafe extern "custom" fn()` - --> $DIR/bad-custom.rs:102:64 + --> $DIR/bad-custom.rs:105:64 | -LL | fn no_promotion_to_fn_trait(f: unsafe extern "custom" fn()) -> impl Fn() { +LL | fn no_promotion_to_fn_trait(f: unsafe extern "custom" fn()) -> impl Fn() { | ^^^^^^^^^ call the function in a closure: `|| unsafe { /* code */ }` LL | LL | f @@ -246,96 +285,96 @@ LL | extern "custom" fn negate(a: i64) -> i64 { | error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:75:14 + --> $DIR/bad-custom.rs:76:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:75:14 + --> $DIR/bad-custom.rs:76:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:80:14 + --> $DIR/bad-custom.rs:82:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:80:14 + --> $DIR/bad-custom.rs:82:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:87:14 + --> $DIR/bad-custom.rs:90:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:87:14 + --> $DIR/bad-custom.rs:90:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:109:20 + --> $DIR/bad-custom.rs:112:20 | LL | assert_eq!(double(21), 42); | ^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:109:20 + --> $DIR/bad-custom.rs:112:20 | LL | assert_eq!(double(21), 42); | ^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:112:29 + --> $DIR/bad-custom.rs:115:29 | LL | assert_eq!(unsafe { increment(41) }, 42); | ^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:112:29 + --> $DIR/bad-custom.rs:115:29 | LL | assert_eq!(unsafe { increment(41) }, 42); | ^^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:115:17 + --> $DIR/bad-custom.rs:118:17 | LL | assert!(Thing(41).is_even()); | ^^^^^^^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:115:17 + --> $DIR/bad-custom.rs:118:17 | LL | assert!(Thing(41).is_even()); | ^^^^^^^^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:118:20 + --> $DIR/bad-custom.rs:121:20 | LL | assert_eq!(Thing::bitwise_not(42), !42); | ^^^^^^^^^^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:118:20 + --> $DIR/bad-custom.rs:121:20 | LL | assert_eq!(Thing::bitwise_not(42), !42); | ^^^^^^^^^^^^^^^^^^^^^^ error[E0015]: inline assembly is not allowed in constant functions - --> $DIR/bad-custom.rs:93:5 + --> $DIR/bad-custom.rs:96:5 | LL | std::arch::naked_asm!("") | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 28 previous errors +error: aborting due to 31 previous errors Some errors have detailed explanations: E0015, E0277. For more information about an error, try `rustc --explain E0015`. diff --git a/tests/ui/abi/cannot-be-called.amdgpu.stderr b/tests/ui/abi/cannot-be-called.amdgpu.stderr index 039e2d7b4f009..b623d8e58d628 100644 --- a/tests/ui/abi/cannot-be-called.amdgpu.stderr +++ b/tests/ui/abi/cannot-be-called.amdgpu.stderr @@ -55,7 +55,7 @@ LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) { error[E0570]: "x86-interrupt" is not a supported ABI for the current target --> $DIR/cannot-be-called.rs:100:22 | -LL | fn x86_ptr(f: extern "x86-interrupt" fn()) { +LL | fn x86_ptr(f: extern "x86-interrupt" fn(*const u8), frame: *const u8) { | ^^^^^^^^^^^^^^^ error: functions with the "gpu-kernel" ABI cannot be called diff --git a/tests/ui/abi/cannot-be-called.avr.stderr b/tests/ui/abi/cannot-be-called.avr.stderr index 752292a58705f..1e18298ebdc69 100644 --- a/tests/ui/abi/cannot-be-called.avr.stderr +++ b/tests/ui/abi/cannot-be-called.avr.stderr @@ -49,7 +49,7 @@ LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) { error[E0570]: "x86-interrupt" is not a supported ABI for the current target --> $DIR/cannot-be-called.rs:100:22 | -LL | fn x86_ptr(f: extern "x86-interrupt" fn()) { +LL | fn x86_ptr(f: extern "x86-interrupt" fn(*const u8), frame: *const u8) { | ^^^^^^^^^^^^^^^ error[E0570]: "gpu-kernel" is not a supported ABI for the current target diff --git a/tests/ui/abi/cannot-be-called.i686.stderr b/tests/ui/abi/cannot-be-called.i686.stderr index 30e294ad61ecc..dfd9cbc47636f 100644 --- a/tests/ui/abi/cannot-be-called.i686.stderr +++ b/tests/ui/abi/cannot-be-called.i686.stderr @@ -73,14 +73,14 @@ LL | x86(&raw const BYTE); error: functions with the "x86-interrupt" ABI cannot be called --> $DIR/cannot-be-called.rs:102:5 | -LL | f() - | ^^^ +LL | f(frame) + | ^^^^^^^^ | note: an `extern "x86-interrupt"` function can only be called using inline assembly --> $DIR/cannot-be-called.rs:102:5 | -LL | f() - | ^^^ +LL | f(frame) + | ^^^^^^^^ error: aborting due to 12 previous errors diff --git a/tests/ui/abi/cannot-be-called.msp430.stderr b/tests/ui/abi/cannot-be-called.msp430.stderr index 12ab88bc858bd..0f3aa256d067f 100644 --- a/tests/ui/abi/cannot-be-called.msp430.stderr +++ b/tests/ui/abi/cannot-be-called.msp430.stderr @@ -49,7 +49,7 @@ LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) { error[E0570]: "x86-interrupt" is not a supported ABI for the current target --> $DIR/cannot-be-called.rs:100:22 | -LL | fn x86_ptr(f: extern "x86-interrupt" fn()) { +LL | fn x86_ptr(f: extern "x86-interrupt" fn(*const u8), frame: *const u8) { | ^^^^^^^^^^^^^^^ error[E0570]: "gpu-kernel" is not a supported ABI for the current target diff --git a/tests/ui/abi/cannot-be-called.nvptx.stderr b/tests/ui/abi/cannot-be-called.nvptx.stderr index 039e2d7b4f009..b623d8e58d628 100644 --- a/tests/ui/abi/cannot-be-called.nvptx.stderr +++ b/tests/ui/abi/cannot-be-called.nvptx.stderr @@ -55,7 +55,7 @@ LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) { error[E0570]: "x86-interrupt" is not a supported ABI for the current target --> $DIR/cannot-be-called.rs:100:22 | -LL | fn x86_ptr(f: extern "x86-interrupt" fn()) { +LL | fn x86_ptr(f: extern "x86-interrupt" fn(*const u8), frame: *const u8) { | ^^^^^^^^^^^^^^^ error: functions with the "gpu-kernel" ABI cannot be called diff --git a/tests/ui/abi/cannot-be-called.riscv32.stderr b/tests/ui/abi/cannot-be-called.riscv32.stderr index b1897b74ebbe3..f067d4aab6335 100644 --- a/tests/ui/abi/cannot-be-called.riscv32.stderr +++ b/tests/ui/abi/cannot-be-called.riscv32.stderr @@ -37,7 +37,7 @@ LL | fn msp430_ptr(f: extern "msp430-interrupt" fn()) { error[E0570]: "x86-interrupt" is not a supported ABI for the current target --> $DIR/cannot-be-called.rs:100:22 | -LL | fn x86_ptr(f: extern "x86-interrupt" fn()) { +LL | fn x86_ptr(f: extern "x86-interrupt" fn(*const u8), frame: *const u8) { | ^^^^^^^^^^^^^^^ error[E0570]: "gpu-kernel" is not a supported ABI for the current target diff --git a/tests/ui/abi/cannot-be-called.riscv64.stderr b/tests/ui/abi/cannot-be-called.riscv64.stderr index b1897b74ebbe3..f067d4aab6335 100644 --- a/tests/ui/abi/cannot-be-called.riscv64.stderr +++ b/tests/ui/abi/cannot-be-called.riscv64.stderr @@ -37,7 +37,7 @@ LL | fn msp430_ptr(f: extern "msp430-interrupt" fn()) { error[E0570]: "x86-interrupt" is not a supported ABI for the current target --> $DIR/cannot-be-called.rs:100:22 | -LL | fn x86_ptr(f: extern "x86-interrupt" fn()) { +LL | fn x86_ptr(f: extern "x86-interrupt" fn(*const u8), frame: *const u8) { | ^^^^^^^^^^^^^^^ error[E0570]: "gpu-kernel" is not a supported ABI for the current target diff --git a/tests/ui/abi/cannot-be-called.rs b/tests/ui/abi/cannot-be-called.rs index 8d1a4ae0b557f..eef2f8c671efa 100644 --- a/tests/ui/abi/cannot-be-called.rs +++ b/tests/ui/abi/cannot-be-called.rs @@ -97,9 +97,9 @@ fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) { //[riscv32,riscv64]~^ ERROR functions with the "riscv-interrupt-s" ABI cannot be called } -fn x86_ptr(f: extern "x86-interrupt" fn()) { +fn x86_ptr(f: extern "x86-interrupt" fn(*const u8), frame: *const u8) { //[riscv32,riscv64,avr,msp430,amdgpu,nvptx]~^ ERROR is not a supported ABI - f() + f(frame) //[x64,x64_win,i686]~^ ERROR functions with the "x86-interrupt" ABI cannot be called } diff --git a/tests/ui/abi/cannot-be-called.x64.stderr b/tests/ui/abi/cannot-be-called.x64.stderr index 30e294ad61ecc..dfd9cbc47636f 100644 --- a/tests/ui/abi/cannot-be-called.x64.stderr +++ b/tests/ui/abi/cannot-be-called.x64.stderr @@ -73,14 +73,14 @@ LL | x86(&raw const BYTE); error: functions with the "x86-interrupt" ABI cannot be called --> $DIR/cannot-be-called.rs:102:5 | -LL | f() - | ^^^ +LL | f(frame) + | ^^^^^^^^ | note: an `extern "x86-interrupt"` function can only be called using inline assembly --> $DIR/cannot-be-called.rs:102:5 | -LL | f() - | ^^^ +LL | f(frame) + | ^^^^^^^^ error: aborting due to 12 previous errors diff --git a/tests/ui/abi/cannot-be-called.x64_win.stderr b/tests/ui/abi/cannot-be-called.x64_win.stderr index 30e294ad61ecc..dfd9cbc47636f 100644 --- a/tests/ui/abi/cannot-be-called.x64_win.stderr +++ b/tests/ui/abi/cannot-be-called.x64_win.stderr @@ -73,14 +73,14 @@ LL | x86(&raw const BYTE); error: functions with the "x86-interrupt" ABI cannot be called --> $DIR/cannot-be-called.rs:102:5 | -LL | f() - | ^^^ +LL | f(frame) + | ^^^^^^^^ | note: an `extern "x86-interrupt"` function can only be called using inline assembly --> $DIR/cannot-be-called.rs:102:5 | -LL | f() - | ^^^ +LL | f(frame) + | ^^^^^^^^ error: aborting due to 12 previous errors diff --git a/tests/ui/abi/interrupt-invalid-signature.avr.stderr b/tests/ui/abi/interrupt-invalid-signature.avr.stderr index 1cf91a33c523b..268d593b9120b 100644 --- a/tests/ui/abi/interrupt-invalid-signature.avr.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.avr.stderr @@ -12,7 +12,7 @@ LL + extern "avr-interrupt" fn avr_arg() {} | error: invalid signature for `extern "avr-interrupt"` function - --> $DIR/interrupt-invalid-signature.rs:60:40 + --> $DIR/interrupt-invalid-signature.rs:59:40 | LL | extern "avr-interrupt" fn avr_ret() -> u8 { | ^^ @@ -24,5 +24,18 @@ LL - extern "avr-interrupt" fn avr_ret() -> u8 { LL + extern "avr-interrupt" fn avr_ret() { | -error: aborting due to 2 previous errors +error: invalid signature for `extern "avr-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:107:42 + | +LL | fn avr_ptr(_f: extern "avr-interrupt" fn(u8) -> u8) { + | ^^ ^^ + | + = note: functions with the "avr-interrupt" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - fn avr_ptr(_f: extern "avr-interrupt" fn(u8) -> u8) { +LL + fn avr_ptr(_f: extern "avr-interrupt" fn()) { + | + +error: aborting due to 3 previous errors diff --git a/tests/ui/abi/interrupt-invalid-signature.i686.stderr b/tests/ui/abi/interrupt-invalid-signature.i686.stderr index 3a8a0c057448b..257d7cf84940c 100644 --- a/tests/ui/abi/interrupt-invalid-signature.i686.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.i686.stderr @@ -1,18 +1,18 @@ error: invalid signature for `extern "x86-interrupt"` function - --> $DIR/interrupt-invalid-signature.rs:84:53 + --> $DIR/interrupt-invalid-signature.rs:83:53 | LL | extern "x86-interrupt" fn x86_ret(_p: *const u8) -> u8 { | ^^ | = note: functions with the "x86-interrupt" ABI cannot have a return type help: remove the return type - --> $DIR/interrupt-invalid-signature.rs:84:53 + --> $DIR/interrupt-invalid-signature.rs:83:53 | LL | extern "x86-interrupt" fn x86_ret(_p: *const u8) -> u8 { | ^^ error: invalid signature for `extern "x86-interrupt"` function - --> $DIR/interrupt-invalid-signature.rs:90:1 + --> $DIR/interrupt-invalid-signature.rs:89:1 | LL | extern "x86-interrupt" fn x86_0() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -20,12 +20,54 @@ LL | extern "x86-interrupt" fn x86_0() { = note: functions with the "x86-interrupt" ABI must be have either 1 or 2 parameters (but found 0) error: invalid signature for `extern "x86-interrupt"` function - --> $DIR/interrupt-invalid-signature.rs:101:33 + --> $DIR/interrupt-invalid-signature.rs:100:33 | LL | extern "x86-interrupt" fn x86_3(_p1: *const u8, _p2: *const u8, _p3: *const u8) { | ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^ | = note: functions with the "x86-interrupt" ABI must be have either 1 or 2 parameters (but found 3) -error: aborting due to 3 previous errors +error: invalid signature for `extern "x86-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:128:24 + | +LL | fn x86_ptr_too_few(_f: extern "x86-interrupt" fn() -> u8) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: functions with the "x86-interrupt" ABI must be have either 1 or 2 parameters (but found 0) + +error: invalid signature for `extern "x86-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:128:55 + | +LL | fn x86_ptr_too_few(_f: extern "x86-interrupt" fn() -> u8) { + | ^^ + | + = note: functions with the "x86-interrupt" ABI cannot have a return type +help: remove the return type + --> $DIR/interrupt-invalid-signature.rs:128:55 + | +LL | fn x86_ptr_too_few(_f: extern "x86-interrupt" fn() -> u8) { + | ^^ + +error: invalid signature for `extern "x86-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:135:51 + | +LL | fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { + | ^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^ + | + = note: functions with the "x86-interrupt" ABI must be have either 1 or 2 parameters (but found 3) + +error: invalid signature for `extern "x86-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:135:87 + | +LL | fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { + | ^^ + | + = note: functions with the "x86-interrupt" ABI cannot have a return type +help: remove the return type + --> $DIR/interrupt-invalid-signature.rs:135:87 + | +LL | fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { + | ^^ + +error: aborting due to 7 previous errors diff --git a/tests/ui/abi/interrupt-invalid-signature.msp430.stderr b/tests/ui/abi/interrupt-invalid-signature.msp430.stderr index 3fff6492b7ea9..568c82e72acea 100644 --- a/tests/ui/abi/interrupt-invalid-signature.msp430.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.msp430.stderr @@ -12,7 +12,7 @@ LL + extern "msp430-interrupt" fn msp430_arg() {} | error: invalid signature for `extern "msp430-interrupt"` function - --> $DIR/interrupt-invalid-signature.rs:66:46 + --> $DIR/interrupt-invalid-signature.rs:65:46 | LL | extern "msp430-interrupt" fn msp430_ret() -> u8 { | ^^ @@ -24,5 +24,18 @@ LL - extern "msp430-interrupt" fn msp430_ret() -> u8 { LL + extern "msp430-interrupt" fn msp430_ret() { | -error: aborting due to 2 previous errors +error: invalid signature for `extern "msp430-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:112:48 + | +LL | fn msp430_ptr(_f: extern "msp430-interrupt" fn(u8) -> u8) { + | ^^ ^^ + | + = note: functions with the "msp430-interrupt" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - fn msp430_ptr(_f: extern "msp430-interrupt" fn(u8) -> u8) { +LL + fn msp430_ptr(_f: extern "msp430-interrupt" fn()) { + | + +error: aborting due to 3 previous errors diff --git a/tests/ui/abi/interrupt-invalid-signature.riscv32.stderr b/tests/ui/abi/interrupt-invalid-signature.riscv32.stderr index 8aea68e65006c..21ec787da22ed 100644 --- a/tests/ui/abi/interrupt-invalid-signature.riscv32.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.riscv32.stderr @@ -25,7 +25,7 @@ LL + extern "riscv-interrupt-s" fn riscv_s_arg() {} | error: invalid signature for `extern "riscv-interrupt-m"` function - --> $DIR/interrupt-invalid-signature.rs:72:48 + --> $DIR/interrupt-invalid-signature.rs:71:48 | LL | extern "riscv-interrupt-m" fn riscv_m_ret() -> u8 { | ^^ @@ -38,7 +38,7 @@ LL + extern "riscv-interrupt-m" fn riscv_m_ret() { | error: invalid signature for `extern "riscv-interrupt-s"` function - --> $DIR/interrupt-invalid-signature.rs:78:48 + --> $DIR/interrupt-invalid-signature.rs:77:48 | LL | extern "riscv-interrupt-s" fn riscv_s_ret() -> u8 { | ^^ @@ -50,5 +50,31 @@ LL - extern "riscv-interrupt-s" fn riscv_s_ret() -> u8 { LL + extern "riscv-interrupt-s" fn riscv_s_ret() { | -error: aborting due to 4 previous errors +error: invalid signature for `extern "riscv-interrupt-m"` function + --> $DIR/interrupt-invalid-signature.rs:117:50 + | +LL | fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn(u8) -> u8) { + | ^^ ^^ + | + = note: functions with the "riscv-interrupt-m" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn(u8) -> u8) { +LL + fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn()) { + | + +error: invalid signature for `extern "riscv-interrupt-s"` function + --> $DIR/interrupt-invalid-signature.rs:122:50 + | +LL | fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn(u8) -> u8) { + | ^^ ^^ + | + = note: functions with the "riscv-interrupt-s" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn(u8) -> u8) { +LL + fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn()) { + | + +error: aborting due to 6 previous errors diff --git a/tests/ui/abi/interrupt-invalid-signature.riscv64.stderr b/tests/ui/abi/interrupt-invalid-signature.riscv64.stderr index 8aea68e65006c..21ec787da22ed 100644 --- a/tests/ui/abi/interrupt-invalid-signature.riscv64.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.riscv64.stderr @@ -25,7 +25,7 @@ LL + extern "riscv-interrupt-s" fn riscv_s_arg() {} | error: invalid signature for `extern "riscv-interrupt-m"` function - --> $DIR/interrupt-invalid-signature.rs:72:48 + --> $DIR/interrupt-invalid-signature.rs:71:48 | LL | extern "riscv-interrupt-m" fn riscv_m_ret() -> u8 { | ^^ @@ -38,7 +38,7 @@ LL + extern "riscv-interrupt-m" fn riscv_m_ret() { | error: invalid signature for `extern "riscv-interrupt-s"` function - --> $DIR/interrupt-invalid-signature.rs:78:48 + --> $DIR/interrupt-invalid-signature.rs:77:48 | LL | extern "riscv-interrupt-s" fn riscv_s_ret() -> u8 { | ^^ @@ -50,5 +50,31 @@ LL - extern "riscv-interrupt-s" fn riscv_s_ret() -> u8 { LL + extern "riscv-interrupt-s" fn riscv_s_ret() { | -error: aborting due to 4 previous errors +error: invalid signature for `extern "riscv-interrupt-m"` function + --> $DIR/interrupt-invalid-signature.rs:117:50 + | +LL | fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn(u8) -> u8) { + | ^^ ^^ + | + = note: functions with the "riscv-interrupt-m" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn(u8) -> u8) { +LL + fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn()) { + | + +error: invalid signature for `extern "riscv-interrupt-s"` function + --> $DIR/interrupt-invalid-signature.rs:122:50 + | +LL | fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn(u8) -> u8) { + | ^^ ^^ + | + = note: functions with the "riscv-interrupt-s" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn(u8) -> u8) { +LL + fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn()) { + | + +error: aborting due to 6 previous errors diff --git a/tests/ui/abi/interrupt-invalid-signature.rs b/tests/ui/abi/interrupt-invalid-signature.rs index 76302f6a4fad2..58aa72de561b5 100644 --- a/tests/ui/abi/interrupt-invalid-signature.rs +++ b/tests/ui/abi/interrupt-invalid-signature.rs @@ -45,15 +45,14 @@ extern "msp430-interrupt" fn msp430_arg(_byte: u8) {} extern "avr-interrupt" fn avr_arg(_byte: u8) {} //[avr]~^ ERROR invalid signature -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] extern "riscv-interrupt-m" fn riscv_m_arg(_byte: u8) {} //[riscv32,riscv64]~^ ERROR invalid signature -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] extern "riscv-interrupt-s" fn riscv_s_arg(_byte: u8) {} //[riscv32,riscv64]~^ ERROR invalid signature - /* all extern "interrupt" definitions should not return non-1ZST values */ #[cfg(avr)] @@ -68,60 +67,72 @@ extern "msp430-interrupt" fn msp430_ret() -> u8 { 1 } -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] extern "riscv-interrupt-m" fn riscv_m_ret() -> u8 { //[riscv32,riscv64]~^ ERROR invalid signature 1 } -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] extern "riscv-interrupt-s" fn riscv_s_ret() -> u8 { //[riscv32,riscv64]~^ ERROR invalid signature 1 } -#[cfg(any(x64,i686))] +#[cfg(any(x64, i686))] extern "x86-interrupt" fn x86_ret(_p: *const u8) -> u8 { //[x64,i686]~^ ERROR invalid signature 1 } -#[cfg(any(x64,i686))] +#[cfg(any(x64, i686))] extern "x86-interrupt" fn x86_0() { //[x64,i686]~^ ERROR invalid signature } -#[cfg(any(x64,i686))] -extern "x86-interrupt" fn x86_1(_p1: *const u8) { } +#[cfg(any(x64, i686))] +extern "x86-interrupt" fn x86_1(_p1: *const u8) {} -#[cfg(any(x64,i686))] -extern "x86-interrupt" fn x86_2(_p1: *const u8, _p2: *const u8) { } +#[cfg(any(x64, i686))] +extern "x86-interrupt" fn x86_2(_p1: *const u8, _p2: *const u8) {} -#[cfg(any(x64,i686))] +#[cfg(any(x64, i686))] extern "x86-interrupt" fn x86_3(_p1: *const u8, _p2: *const u8, _p3: *const u8) { //[x64,i686]~^ ERROR invalid signature } - - /* extern "interrupt" fnptrs with invalid signatures */ #[cfg(avr)] fn avr_ptr(_f: extern "avr-interrupt" fn(u8) -> u8) { + //[avr]~^ ERROR invalid signature } #[cfg(msp430)] fn msp430_ptr(_f: extern "msp430-interrupt" fn(u8) -> u8) { + //[msp430]~^ ERROR invalid signature } -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn(u8) -> u8) { + //[riscv32,riscv64]~^ ERROR invalid signature } -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn(u8) -> u8) { + //[riscv32,riscv64]~^ ERROR invalid signature } -#[cfg(any(x64,i686))] -fn x86_ptr(_f: extern "x86-interrupt" fn() -> u8) { +// One error for the argument list, another for the return type. +#[cfg(any(x64, i686))] +fn x86_ptr_too_few(_f: extern "x86-interrupt" fn() -> u8) { + //[x64,i686]~^ ERROR invalid signature + //[x64,i686]~| ERROR invalid signature +} + +// One error for the argument list, another for the return type. +#[cfg(any(x64, i686))] +fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { + //[x64,i686]~^ ERROR invalid signature + //[x64,i686]~| ERROR invalid signature } diff --git a/tests/ui/abi/interrupt-invalid-signature.x64.stderr b/tests/ui/abi/interrupt-invalid-signature.x64.stderr index 3a8a0c057448b..257d7cf84940c 100644 --- a/tests/ui/abi/interrupt-invalid-signature.x64.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.x64.stderr @@ -1,18 +1,18 @@ error: invalid signature for `extern "x86-interrupt"` function - --> $DIR/interrupt-invalid-signature.rs:84:53 + --> $DIR/interrupt-invalid-signature.rs:83:53 | LL | extern "x86-interrupt" fn x86_ret(_p: *const u8) -> u8 { | ^^ | = note: functions with the "x86-interrupt" ABI cannot have a return type help: remove the return type - --> $DIR/interrupt-invalid-signature.rs:84:53 + --> $DIR/interrupt-invalid-signature.rs:83:53 | LL | extern "x86-interrupt" fn x86_ret(_p: *const u8) -> u8 { | ^^ error: invalid signature for `extern "x86-interrupt"` function - --> $DIR/interrupt-invalid-signature.rs:90:1 + --> $DIR/interrupt-invalid-signature.rs:89:1 | LL | extern "x86-interrupt" fn x86_0() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -20,12 +20,54 @@ LL | extern "x86-interrupt" fn x86_0() { = note: functions with the "x86-interrupt" ABI must be have either 1 or 2 parameters (but found 0) error: invalid signature for `extern "x86-interrupt"` function - --> $DIR/interrupt-invalid-signature.rs:101:33 + --> $DIR/interrupt-invalid-signature.rs:100:33 | LL | extern "x86-interrupt" fn x86_3(_p1: *const u8, _p2: *const u8, _p3: *const u8) { | ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^ | = note: functions with the "x86-interrupt" ABI must be have either 1 or 2 parameters (but found 3) -error: aborting due to 3 previous errors +error: invalid signature for `extern "x86-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:128:24 + | +LL | fn x86_ptr_too_few(_f: extern "x86-interrupt" fn() -> u8) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: functions with the "x86-interrupt" ABI must be have either 1 or 2 parameters (but found 0) + +error: invalid signature for `extern "x86-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:128:55 + | +LL | fn x86_ptr_too_few(_f: extern "x86-interrupt" fn() -> u8) { + | ^^ + | + = note: functions with the "x86-interrupt" ABI cannot have a return type +help: remove the return type + --> $DIR/interrupt-invalid-signature.rs:128:55 + | +LL | fn x86_ptr_too_few(_f: extern "x86-interrupt" fn() -> u8) { + | ^^ + +error: invalid signature for `extern "x86-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:135:51 + | +LL | fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { + | ^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^ + | + = note: functions with the "x86-interrupt" ABI must be have either 1 or 2 parameters (but found 3) + +error: invalid signature for `extern "x86-interrupt"` function + --> $DIR/interrupt-invalid-signature.rs:135:87 + | +LL | fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { + | ^^ + | + = note: functions with the "x86-interrupt" ABI cannot have a return type +help: remove the return type + --> $DIR/interrupt-invalid-signature.rs:135:87 + | +LL | fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { + | ^^ + +error: aborting due to 7 previous errors diff --git a/tests/ui/abi/interrupt-returns-never-or-unit.rs b/tests/ui/abi/interrupt-returns-never-or-unit.rs index 29713f2828ba2..75786730a2ca4 100644 --- a/tests/ui/abi/interrupt-returns-never-or-unit.rs +++ b/tests/ui/abi/interrupt-returns-never-or-unit.rs @@ -46,17 +46,17 @@ extern "msp430-interrupt" fn msp430_ret_never() -> ! { loop {} } -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] extern "riscv-interrupt-m" fn riscv_m_ret_never() -> ! { loop {} } -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] extern "riscv-interrupt-s" fn riscv_s_ret_never() -> ! { loop {} } -#[cfg(any(x64,i686))] +#[cfg(any(x64, i686))] extern "x86-interrupt" fn x86_ret_never(_p: *const u8) -> ! { loop {} } @@ -73,17 +73,17 @@ extern "msp430-interrupt" fn msp430_ret_unit() -> () { () } -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] extern "riscv-interrupt-m" fn riscv_m_ret_unit() -> () { () } -#[cfg(any(riscv32,riscv64))] +#[cfg(any(riscv32, riscv64))] extern "riscv-interrupt-s" fn riscv_s_ret_unit() -> () { () } -#[cfg(any(x64,i686))] +#[cfg(any(x64, i686))] extern "x86-interrupt" fn x86_ret_unit(_x: *const u8) -> () { () } @@ -91,21 +91,16 @@ extern "x86-interrupt" fn x86_ret_unit(_x: *const u8) -> () { /* extern "interrupt" fnptrs can return ! too */ #[cfg(avr)] -fn avr_ptr(_f: extern "avr-interrupt" fn() -> !) { -} +fn avr_ptr(_f: extern "avr-interrupt" fn() -> !) {} #[cfg(msp430)] -fn msp430_ptr(_f: extern "msp430-interrupt" fn() -> !) { -} +fn msp430_ptr(_f: extern "msp430-interrupt" fn() -> !) {} -#[cfg(any(riscv32,riscv64))] -fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn() -> !) { -} +#[cfg(any(riscv32, riscv64))] +fn riscv_m_ptr(_f: extern "riscv-interrupt-m" fn() -> !) {} -#[cfg(any(riscv32,riscv64))] -fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn() -> !) { -} +#[cfg(any(riscv32, riscv64))] +fn riscv_s_ptr(_f: extern "riscv-interrupt-s" fn() -> !) {} -#[cfg(any(x64,i686))] -fn x86_ptr(_f: extern "x86-interrupt" fn() -> !) { -} +#[cfg(any(x64, i686))] +fn x86_ptr(_f: extern "x86-interrupt" fn(*const u8) -> !) {} diff --git a/tests/ui/abi/unsupported.rs b/tests/ui/abi/unsupported.rs index 661691956cb5d..a0b9e14b912e5 100644 --- a/tests/ui/abi/unsupported.rs +++ b/tests/ui/abi/unsupported.rs @@ -143,7 +143,7 @@ extern "cdecl" {} //[x64_win]~^ WARN unsupported_calling_conventions //[x64_win]~^^ WARN this was previously accepted -fn custom_ptr(f: extern "custom" fn()) { +fn custom_ptr(f: unsafe extern "custom" fn()) { //[wasm32,wasm64]~^ ERROR is not a supported ABI let _ = f; } diff --git a/tests/ui/abi/unsupported.wasm32.stderr b/tests/ui/abi/unsupported.wasm32.stderr index 9314c287b8591..826658ba7de87 100644 --- a/tests/ui/abi/unsupported.wasm32.stderr +++ b/tests/ui/abi/unsupported.wasm32.stderr @@ -157,10 +157,10 @@ LL | extern "cmse-nonsecure-entry" {} | ^^^^^^^^^^^^^^^^^^^^^^ error[E0570]: "custom" is not a supported ABI for the current target - --> $DIR/unsupported.rs:146:25 + --> $DIR/unsupported.rs:146:32 | -LL | fn custom_ptr(f: extern "custom" fn()) { - | ^^^^^^^^ +LL | fn custom_ptr(f: unsafe extern "custom" fn()) { + | ^^^^^^^^ error[E0570]: "custom" is not a supported ABI for the current target --> $DIR/unsupported.rs:150:8 diff --git a/tests/ui/abi/unsupported.wasm64.stderr b/tests/ui/abi/unsupported.wasm64.stderr index 9314c287b8591..826658ba7de87 100644 --- a/tests/ui/abi/unsupported.wasm64.stderr +++ b/tests/ui/abi/unsupported.wasm64.stderr @@ -157,10 +157,10 @@ LL | extern "cmse-nonsecure-entry" {} | ^^^^^^^^^^^^^^^^^^^^^^ error[E0570]: "custom" is not a supported ABI for the current target - --> $DIR/unsupported.rs:146:25 + --> $DIR/unsupported.rs:146:32 | -LL | fn custom_ptr(f: extern "custom" fn()) { - | ^^^^^^^^ +LL | fn custom_ptr(f: unsafe extern "custom" fn()) { + | ^^^^^^^^ error[E0570]: "custom" is not a supported ABI for the current target --> $DIR/unsupported.rs:150:8 diff --git a/tests/ui/feature-gates/feature-gate-abi-custom.rs b/tests/ui/feature-gates/feature-gate-abi-custom.rs index ca0b8337bbb75..f5e3c8e28b47c 100644 --- a/tests/ui/feature-gates/feature-gate-abi-custom.rs +++ b/tests/ui/feature-gates/feature-gate-abi-custom.rs @@ -46,6 +46,6 @@ impl S { } } -type A7 = extern "custom" fn(); //~ ERROR "custom" ABI is experimental +type A7 = unsafe extern "custom" fn(); //~ ERROR "custom" ABI is experimental extern "custom" {} //~ ERROR "custom" ABI is experimental diff --git a/tests/ui/feature-gates/feature-gate-abi-custom.stderr b/tests/ui/feature-gates/feature-gate-abi-custom.stderr index e359dbb5ebe1d..cee258d843b8e 100644 --- a/tests/ui/feature-gates/feature-gate-abi-custom.stderr +++ b/tests/ui/feature-gates/feature-gate-abi-custom.stderr @@ -93,10 +93,10 @@ LL | extern "custom" fn im7() { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: the extern "custom" ABI is experimental and subject to change - --> $DIR/feature-gate-abi-custom.rs:49:18 + --> $DIR/feature-gate-abi-custom.rs:49:25 | -LL | type A7 = extern "custom" fn(); - | ^^^^^^^^ +LL | type A7 = unsafe extern "custom" fn(); + | ^^^^^^^^ | = note: see issue #140829 for more information = help: add `#![feature(abi_custom)]` to the crate attributes to enable diff --git a/tests/ui/feature-gates/feature-gate-abi-x86-interrupt.rs b/tests/ui/feature-gates/feature-gate-abi-x86-interrupt.rs index 2bfe03b891108..2f5d01ed4cc99 100644 --- a/tests/ui/feature-gates/feature-gate-abi-x86-interrupt.rs +++ b/tests/ui/feature-gates/feature-gate-abi-x86-interrupt.rs @@ -25,6 +25,6 @@ impl S { extern "x86-interrupt" fn im7(_p: *const u8) {} //~ ERROR "x86-interrupt" ABI is experimental } -type A7 = extern "x86-interrupt" fn(); //~ ERROR "x86-interrupt" ABI is experimental +type A7 = extern "x86-interrupt" fn(*const u8); //~ ERROR "x86-interrupt" ABI is experimental extern "x86-interrupt" {} //~ ERROR "x86-interrupt" ABI is experimental diff --git a/tests/ui/feature-gates/feature-gate-abi-x86-interrupt.stderr b/tests/ui/feature-gates/feature-gate-abi-x86-interrupt.stderr index b53917dda610f..17b644b69a59e 100644 --- a/tests/ui/feature-gates/feature-gate-abi-x86-interrupt.stderr +++ b/tests/ui/feature-gates/feature-gate-abi-x86-interrupt.stderr @@ -51,7 +51,7 @@ LL | extern "x86-interrupt" fn im7(_p: *const u8) {} error[E0658]: the extern "x86-interrupt" ABI is experimental and subject to change --> $DIR/feature-gate-abi-x86-interrupt.rs:28:18 | -LL | type A7 = extern "x86-interrupt" fn(); +LL | type A7 = extern "x86-interrupt" fn(*const u8); | ^^^^^^^^^^^^^^^ | = note: see issue #40180 for more information From 55c729f65b5dfd07ebf871e59be15c27b4ff8fba Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 23 Jul 2026 20:53:54 +0200 Subject: [PATCH 56/61] disallow `!` (i.e. `Never`) as a `extern "custom"` return type --- .../rustc_ast_passes/src/ast_validation.rs | 13 +-- tests/ui/abi/bad-custom.rs | 6 ++ tests/ui/abi/bad-custom.stderr | 83 +++++++++++-------- 3 files changed, 62 insertions(+), 40 deletions(-) diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 047212ac9d4ed..1e5fb328950d8 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -677,12 +677,15 @@ impl<'a> AstValidator<'a> { sig: &BorrowedFnSig<'_>, ) { let mut spans: Vec<_> = sig.decl.inputs.iter().map(|p| p.span).collect(); + + let allowed_return = |ret_ty: &Ty| match &ret_ty.kind { + TyKind::Never if abi != ExternAbi::Custom => true, + TyKind::Tup(tup) if tup.is_empty() => true, + _ => false, + }; + if let FnRetTy::Ty(ref ret_ty) = sig.decl.output - && match &ret_ty.kind { - TyKind::Never => false, - TyKind::Tup(tup) if tup.is_empty() => false, - _ => true, - } + && !allowed_return(ret_ty) { spans.push(ret_ty.span); } diff --git a/tests/ui/abi/bad-custom.rs b/tests/ui/abi/bad-custom.rs index cf026f7ae6536..dadeecd1a8572 100644 --- a/tests/ui/abi/bad-custom.rs +++ b/tests/ui/abi/bad-custom.rs @@ -22,6 +22,12 @@ unsafe extern "custom" fn no_return_type() -> i64 { std::arch::naked_asm!("") } +#[unsafe(naked)] +unsafe extern "custom" fn no_never_return_type() -> ! { + //~^ ERROR invalid signature for `extern "custom"` function + std::arch::naked_asm!("") +} + unsafe extern "custom" fn double(a: i64) -> i64 { //~^ ERROR items with the "custom" ABI can only be declared externally or defined via naked functions //~| ERROR invalid signature for `extern "custom"` function diff --git a/tests/ui/abi/bad-custom.stderr b/tests/ui/abi/bad-custom.stderr index 9da50e93bd5ed..bc375984403f6 100644 --- a/tests/ui/abi/bad-custom.stderr +++ b/tests/ui/abi/bad-custom.stderr @@ -49,7 +49,20 @@ LL + unsafe extern "custom" fn no_return_type() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:25:34 + --> $DIR/bad-custom.rs:26:53 + | +LL | unsafe extern "custom" fn no_never_return_type() -> ! { + | ^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - unsafe extern "custom" fn no_never_return_type() -> ! { +LL + unsafe extern "custom" fn no_never_return_type() { + | + +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:31:34 | LL | unsafe extern "custom" fn double(a: i64) -> i64 { | ^^^^^^ ^^^ @@ -62,7 +75,7 @@ LL + unsafe extern "custom" fn double() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:34:39 + --> $DIR/bad-custom.rs:40:39 | LL | unsafe extern "custom" fn is_even(self) -> bool { | ^^^^ ^^^^ @@ -75,7 +88,7 @@ LL + unsafe extern "custom" fn is_even() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:42:43 + --> $DIR/bad-custom.rs:48:43 | LL | unsafe extern "custom" fn bitwise_not(a: i64) -> i64 { | ^^^^^^ ^^^ @@ -88,7 +101,7 @@ LL + unsafe extern "custom" fn bitwise_not() { | error: functions with the "custom" ABI must be unsafe - --> $DIR/bad-custom.rs:52:5 + --> $DIR/bad-custom.rs:58:5 | LL | extern "custom" fn negate(a: i64) -> i64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -99,7 +112,7 @@ LL | unsafe extern "custom" fn negate(a: i64) -> i64; | ++++++ error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:52:31 + --> $DIR/bad-custom.rs:58:31 | LL | extern "custom" fn negate(a: i64) -> i64; | ^^^^^^ ^^^ @@ -112,7 +125,7 @@ LL + extern "custom" fn negate(); | error: functions with the "custom" ABI must be unsafe - --> $DIR/bad-custom.rs:58:5 + --> $DIR/bad-custom.rs:64:5 | LL | extern "custom" fn negate(a: i64) -> i64 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -123,7 +136,7 @@ LL | unsafe extern "custom" fn negate(a: i64) -> i64 { | ++++++ error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:58:31 + --> $DIR/bad-custom.rs:64:31 | LL | extern "custom" fn negate(a: i64) -> i64 { | ^^^^^^ ^^^ @@ -136,7 +149,7 @@ LL + extern "custom" fn negate() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:67:18 + --> $DIR/bad-custom.rs:73:18 | LL | fn increment(a: i64) -> i64; | ^^^^^^ ^^^ @@ -149,7 +162,7 @@ LL + fn increment(); | error: foreign functions with the "custom" ABI cannot be safe - --> $DIR/bad-custom.rs:70:5 + --> $DIR/bad-custom.rs:76:5 | LL | safe fn extern_cannot_be_safe(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -161,7 +174,7 @@ LL + fn extern_cannot_be_safe(); | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:74:40 + --> $DIR/bad-custom.rs:80:40 | LL | fn caller(f: unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { | ^^^ ^^^ @@ -174,7 +187,7 @@ LL + fn caller(f: unsafe extern "custom" fn(), mut x: i64) -> i64 { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:80:48 + --> $DIR/bad-custom.rs:86:48 | LL | fn caller_by_ref(f: &unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { | ^^^ ^^^ @@ -187,7 +200,7 @@ LL + fn caller_by_ref(f: &unsafe extern "custom" fn(), mut x: i64) -> i64 { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:86:41 + --> $DIR/bad-custom.rs:92:41 | LL | type Custom = unsafe extern "custom" fn(i64) -> i64; | ^^^ ^^^ @@ -200,7 +213,7 @@ LL + type Custom = unsafe extern "custom" fn(); | error: functions with the "custom" ABI cannot be `async` - --> $DIR/bad-custom.rs:100:1 + --> $DIR/bad-custom.rs:106:1 | LL | async unsafe extern "custom" fn no_async_fn() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -212,7 +225,7 @@ LL + unsafe extern "custom" fn no_async_fn() { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:100:1 + --> $DIR/bad-custom.rs:106:1 | LL | async unsafe extern "custom" fn no_async_fn() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -224,7 +237,7 @@ LL | async unsafe extern "custom" fn no_async_fn() { | error[E0277]: expected an `Fn()` closure, found `unsafe extern "custom" fn()` - --> $DIR/bad-custom.rs:105:64 + --> $DIR/bad-custom.rs:111:64 | LL | fn no_promotion_to_fn_trait(f: unsafe extern "custom" fn()) -> impl Fn() { | ^^^^^^^^^ call the function in a closure: `|| unsafe { /* code */ }` @@ -237,7 +250,7 @@ LL | f = note: unsafe function cannot be called generically without an unsafe block error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:25:1 + --> $DIR/bad-custom.rs:31:1 | LL | unsafe extern "custom" fn double(a: i64) -> i64 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -249,7 +262,7 @@ LL | unsafe extern "custom" fn double(a: i64) -> i64 { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:34:5 + --> $DIR/bad-custom.rs:40:5 | LL | unsafe extern "custom" fn is_even(self) -> bool { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -261,7 +274,7 @@ LL | unsafe extern "custom" fn is_even(self) -> bool { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:42:5 + --> $DIR/bad-custom.rs:48:5 | LL | unsafe extern "custom" fn bitwise_not(a: i64) -> i64 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -273,7 +286,7 @@ LL | unsafe extern "custom" fn bitwise_not(a: i64) -> i64 { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:58:5 + --> $DIR/bad-custom.rs:64:5 | LL | extern "custom" fn negate(a: i64) -> i64 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -285,96 +298,96 @@ LL | extern "custom" fn negate(a: i64) -> i64 { | error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:76:14 + --> $DIR/bad-custom.rs:82:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:76:14 + --> $DIR/bad-custom.rs:82:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:82:14 + --> $DIR/bad-custom.rs:88:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:82:14 + --> $DIR/bad-custom.rs:88:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:90:14 + --> $DIR/bad-custom.rs:96:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:90:14 + --> $DIR/bad-custom.rs:96:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:112:20 + --> $DIR/bad-custom.rs:118:20 | LL | assert_eq!(double(21), 42); | ^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:112:20 + --> $DIR/bad-custom.rs:118:20 | LL | assert_eq!(double(21), 42); | ^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:115:29 + --> $DIR/bad-custom.rs:121:29 | LL | assert_eq!(unsafe { increment(41) }, 42); | ^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:115:29 + --> $DIR/bad-custom.rs:121:29 | LL | assert_eq!(unsafe { increment(41) }, 42); | ^^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:118:17 + --> $DIR/bad-custom.rs:124:17 | LL | assert!(Thing(41).is_even()); | ^^^^^^^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:118:17 + --> $DIR/bad-custom.rs:124:17 | LL | assert!(Thing(41).is_even()); | ^^^^^^^^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:121:20 + --> $DIR/bad-custom.rs:127:20 | LL | assert_eq!(Thing::bitwise_not(42), !42); | ^^^^^^^^^^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:121:20 + --> $DIR/bad-custom.rs:127:20 | LL | assert_eq!(Thing::bitwise_not(42), !42); | ^^^^^^^^^^^^^^^^^^^^^^ error[E0015]: inline assembly is not allowed in constant functions - --> $DIR/bad-custom.rs:96:5 + --> $DIR/bad-custom.rs:102:5 | LL | std::arch::naked_asm!("") | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 31 previous errors +error: aborting due to 32 previous errors Some errors have detailed explanations: E0015, E0277. For more information about an error, try `rustc --explain E0015`. From a2c59e9d6c84c9c80b7f7c219157537fa3d29c07 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 23 Jul 2026 20:54:57 +0200 Subject: [PATCH 57/61] test `extern "custom"` restrictions on function pointers --- tests/ui/abi/bad-custom.rs | 30 ++++- tests/ui/abi/bad-custom.stderr | 230 +++++++++++++++++++++++---------- 2 files changed, 188 insertions(+), 72 deletions(-) diff --git a/tests/ui/abi/bad-custom.rs b/tests/ui/abi/bad-custom.rs index dadeecd1a8572..a9e8ae2fd3bf8 100644 --- a/tests/ui/abi/bad-custom.rs +++ b/tests/ui/abi/bad-custom.rs @@ -10,44 +10,62 @@ extern "custom" fn must_be_unsafe(a: i64) -> i64 { std::arch::naked_asm!("") } +type MustbeUnsafe = extern "custom" fn(i64) -> i64; +//~^ ERROR functions with the "custom" ABI must be unsafe +//~| ERROR invalid signature for `extern "custom"` function + #[unsafe(naked)] unsafe extern "custom" fn no_parameters(a: i64) { //~^ ERROR invalid signature for `extern "custom"` function std::arch::naked_asm!("") } +type NoParameters = unsafe extern "custom" fn(i64); +//~^ ERROR invalid signature for `extern "custom"` function + #[unsafe(naked)] unsafe extern "custom" fn no_return_type() -> i64 { //~^ ERROR invalid signature for `extern "custom"` function std::arch::naked_asm!("") } +type NoReturnType = unsafe extern "custom" fn() -> i64; +//~^ ERROR invalid signature for `extern "custom"` function + #[unsafe(naked)] unsafe extern "custom" fn no_never_return_type() -> ! { //~^ ERROR invalid signature for `extern "custom"` function std::arch::naked_asm!("") } -unsafe extern "custom" fn double(a: i64) -> i64 { +type NoNeverReturnType = unsafe extern "custom" fn() -> !; +//~^ ERROR invalid signature for `extern "custom"` function + +unsafe extern "custom" fn not_both(a: i64) -> i64 { //~^ ERROR items with the "custom" ABI can only be declared externally or defined via naked functions //~| ERROR invalid signature for `extern "custom"` function unimplemented!() } +type NotBoth = unsafe extern "custom" fn(i64) -> i64; +//~^ ERROR invalid signature for `extern "custom"` function + struct Thing(i64); impl Thing { - unsafe extern "custom" fn is_even(self) -> bool { + extern "custom" fn is_even(self) -> bool { //~^ ERROR items with the "custom" ABI can only be declared externally or defined via naked functions //~| ERROR invalid signature for `extern "custom"` function + //~| ERROR functions with the "custom" ABI must be unsafe unimplemented!() } } trait BitwiseNot { - unsafe extern "custom" fn bitwise_not(a: i64) -> i64 { + extern "custom" fn bitwise_not(a: i64) -> i64 { //~^ ERROR items with the "custom" ABI can only be declared externally or defined via naked functions //~| ERROR invalid signature for `extern "custom"` function + //~| ERROR functions with the "custom" ABI must be unsafe unimplemented!() } } @@ -89,10 +107,10 @@ fn caller_by_ref(f: &unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { //~^ ERROR functions with the "custom" ABI cannot be called } -type Custom = unsafe extern "custom" fn(i64) -> i64; +type Alias = unsafe extern "custom" fn(i64) -> i64; //~^ ERROR invalid signature for `extern "custom"` function -fn caller_alias(f: Custom, mut x: i64) -> i64 { +fn caller_alias(f: Alias, mut x: i64) -> i64 { unsafe { f(x) } //~^ ERROR functions with the "custom" ABI cannot be called } @@ -115,7 +133,7 @@ fn no_promotion_to_fn_trait(f: unsafe extern "custom" fn()) -> impl Fn() { pub fn main() { unsafe { - assert_eq!(double(21), 42); + assert_eq!(not_both(21), 42); //~^ ERROR functions with the "custom" ABI cannot be called assert_eq!(unsafe { increment(41) }, 42); diff --git a/tests/ui/abi/bad-custom.stderr b/tests/ui/abi/bad-custom.stderr index bc375984403f6..1453382285624 100644 --- a/tests/ui/abi/bad-custom.stderr +++ b/tests/ui/abi/bad-custom.stderr @@ -22,8 +22,32 @@ LL - extern "custom" fn must_be_unsafe(a: i64) -> i64 { LL + extern "custom" fn must_be_unsafe() { | +error: functions with the "custom" ABI must be unsafe + --> $DIR/bad-custom.rs:13:21 + | +LL | type MustbeUnsafe = extern "custom" fn(i64) -> i64; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: add the `unsafe` keyword to this definition + | +LL | type MustbeUnsafe = unsafe extern "custom" fn(i64) -> i64; + | ++++++ + error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:14:41 + --> $DIR/bad-custom.rs:13:40 + | +LL | type MustbeUnsafe = extern "custom" fn(i64) -> i64; + | ^^^ ^^^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - type MustbeUnsafe = extern "custom" fn(i64) -> i64; +LL + type MustbeUnsafe = extern "custom" fn(); + | + +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:18:41 | LL | unsafe extern "custom" fn no_parameters(a: i64) { | ^^^^^^ @@ -36,7 +60,20 @@ LL + unsafe extern "custom" fn no_parameters() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:20:47 + --> $DIR/bad-custom.rs:23:47 + | +LL | type NoParameters = unsafe extern "custom" fn(i64); + | ^^^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - type NoParameters = unsafe extern "custom" fn(i64); +LL + type NoParameters = unsafe extern "custom" fn(); + | + +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:27:47 | LL | unsafe extern "custom" fn no_return_type() -> i64 { | ^^^ @@ -49,7 +86,20 @@ LL + unsafe extern "custom" fn no_return_type() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:26:53 + --> $DIR/bad-custom.rs:32:52 + | +LL | type NoReturnType = unsafe extern "custom" fn() -> i64; + | ^^^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - type NoReturnType = unsafe extern "custom" fn() -> i64; +LL + type NoReturnType = unsafe extern "custom" fn(); + | + +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:36:53 | LL | unsafe extern "custom" fn no_never_return_type() -> ! { | ^ @@ -62,46 +112,94 @@ LL + unsafe extern "custom" fn no_never_return_type() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:31:34 + --> $DIR/bad-custom.rs:41:57 | -LL | unsafe extern "custom" fn double(a: i64) -> i64 { - | ^^^^^^ ^^^ +LL | type NoNeverReturnType = unsafe extern "custom" fn() -> !; + | ^ | = note: functions with the "custom" ABI cannot have any parameters or return type help: remove the parameters and return type | -LL - unsafe extern "custom" fn double(a: i64) -> i64 { -LL + unsafe extern "custom" fn double() { +LL - type NoNeverReturnType = unsafe extern "custom" fn() -> !; +LL + type NoNeverReturnType = unsafe extern "custom" fn(); | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:40:39 + --> $DIR/bad-custom.rs:44:36 | -LL | unsafe extern "custom" fn is_even(self) -> bool { - | ^^^^ ^^^^ +LL | unsafe extern "custom" fn not_both(a: i64) -> i64 { + | ^^^^^^ ^^^ | = note: functions with the "custom" ABI cannot have any parameters or return type help: remove the parameters and return type | -LL - unsafe extern "custom" fn is_even(self) -> bool { -LL + unsafe extern "custom" fn is_even() { +LL - unsafe extern "custom" fn not_both(a: i64) -> i64 { +LL + unsafe extern "custom" fn not_both() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:48:43 + --> $DIR/bad-custom.rs:50:42 + | +LL | type NotBoth = unsafe extern "custom" fn(i64) -> i64; + | ^^^ ^^^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - type NotBoth = unsafe extern "custom" fn(i64) -> i64; +LL + type NotBoth = unsafe extern "custom" fn(); + | + +error: functions with the "custom" ABI must be unsafe + --> $DIR/bad-custom.rs:56:5 + | +LL | extern "custom" fn is_even(self) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: add the `unsafe` keyword to this definition + | +LL | unsafe extern "custom" fn is_even(self) -> bool { + | ++++++ + +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:56:32 + | +LL | extern "custom" fn is_even(self) -> bool { + | ^^^^ ^^^^ + | + = note: functions with the "custom" ABI cannot have any parameters or return type +help: remove the parameters and return type + | +LL - extern "custom" fn is_even(self) -> bool { +LL + extern "custom" fn is_even() { + | + +error: functions with the "custom" ABI must be unsafe + --> $DIR/bad-custom.rs:65:5 + | +LL | extern "custom" fn bitwise_not(a: i64) -> i64 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: add the `unsafe` keyword to this definition | LL | unsafe extern "custom" fn bitwise_not(a: i64) -> i64 { - | ^^^^^^ ^^^ + | ++++++ + +error: invalid signature for `extern "custom"` function + --> $DIR/bad-custom.rs:65:36 + | +LL | extern "custom" fn bitwise_not(a: i64) -> i64 { + | ^^^^^^ ^^^ | = note: functions with the "custom" ABI cannot have any parameters or return type help: remove the parameters and return type | -LL - unsafe extern "custom" fn bitwise_not(a: i64) -> i64 { -LL + unsafe extern "custom" fn bitwise_not() { +LL - extern "custom" fn bitwise_not(a: i64) -> i64 { +LL + extern "custom" fn bitwise_not() { | error: functions with the "custom" ABI must be unsafe - --> $DIR/bad-custom.rs:58:5 + --> $DIR/bad-custom.rs:76:5 | LL | extern "custom" fn negate(a: i64) -> i64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -112,7 +210,7 @@ LL | unsafe extern "custom" fn negate(a: i64) -> i64; | ++++++ error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:58:31 + --> $DIR/bad-custom.rs:76:31 | LL | extern "custom" fn negate(a: i64) -> i64; | ^^^^^^ ^^^ @@ -125,7 +223,7 @@ LL + extern "custom" fn negate(); | error: functions with the "custom" ABI must be unsafe - --> $DIR/bad-custom.rs:64:5 + --> $DIR/bad-custom.rs:82:5 | LL | extern "custom" fn negate(a: i64) -> i64 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -136,7 +234,7 @@ LL | unsafe extern "custom" fn negate(a: i64) -> i64 { | ++++++ error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:64:31 + --> $DIR/bad-custom.rs:82:31 | LL | extern "custom" fn negate(a: i64) -> i64 { | ^^^^^^ ^^^ @@ -149,7 +247,7 @@ LL + extern "custom" fn negate() { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:73:18 + --> $DIR/bad-custom.rs:91:18 | LL | fn increment(a: i64) -> i64; | ^^^^^^ ^^^ @@ -162,7 +260,7 @@ LL + fn increment(); | error: foreign functions with the "custom" ABI cannot be safe - --> $DIR/bad-custom.rs:76:5 + --> $DIR/bad-custom.rs:94:5 | LL | safe fn extern_cannot_be_safe(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -174,7 +272,7 @@ LL + fn extern_cannot_be_safe(); | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:80:40 + --> $DIR/bad-custom.rs:98:40 | LL | fn caller(f: unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { | ^^^ ^^^ @@ -187,7 +285,7 @@ LL + fn caller(f: unsafe extern "custom" fn(), mut x: i64) -> i64 { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:86:48 + --> $DIR/bad-custom.rs:104:48 | LL | fn caller_by_ref(f: &unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { | ^^^ ^^^ @@ -200,20 +298,20 @@ LL + fn caller_by_ref(f: &unsafe extern "custom" fn(), mut x: i64) -> i64 { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:92:41 + --> $DIR/bad-custom.rs:110:40 | -LL | type Custom = unsafe extern "custom" fn(i64) -> i64; - | ^^^ ^^^ +LL | type Alias = unsafe extern "custom" fn(i64) -> i64; + | ^^^ ^^^ | = note: functions with the "custom" ABI cannot have any parameters or return type help: remove the parameters and return type | -LL - type Custom = unsafe extern "custom" fn(i64) -> i64; -LL + type Custom = unsafe extern "custom" fn(); +LL - type Alias = unsafe extern "custom" fn(i64) -> i64; +LL + type Alias = unsafe extern "custom" fn(); | error: functions with the "custom" ABI cannot be `async` - --> $DIR/bad-custom.rs:106:1 + --> $DIR/bad-custom.rs:124:1 | LL | async unsafe extern "custom" fn no_async_fn() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -225,7 +323,7 @@ LL + unsafe extern "custom" fn no_async_fn() { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:106:1 + --> $DIR/bad-custom.rs:124:1 | LL | async unsafe extern "custom" fn no_async_fn() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -237,7 +335,7 @@ LL | async unsafe extern "custom" fn no_async_fn() { | error[E0277]: expected an `Fn()` closure, found `unsafe extern "custom" fn()` - --> $DIR/bad-custom.rs:111:64 + --> $DIR/bad-custom.rs:129:64 | LL | fn no_promotion_to_fn_trait(f: unsafe extern "custom" fn()) -> impl Fn() { | ^^^^^^^^^ call the function in a closure: `|| unsafe { /* code */ }` @@ -250,43 +348,43 @@ LL | f = note: unsafe function cannot be called generically without an unsafe block error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:31:1 + --> $DIR/bad-custom.rs:44:1 | -LL | unsafe extern "custom" fn double(a: i64) -> i64 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe extern "custom" fn not_both(a: i64) -> i64 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: convert this to an `#[unsafe(naked)]` function | LL + #[unsafe(naked)] -LL | unsafe extern "custom" fn double(a: i64) -> i64 { +LL | unsafe extern "custom" fn not_both(a: i64) -> i64 { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:40:5 + --> $DIR/bad-custom.rs:56:5 | -LL | unsafe extern "custom" fn is_even(self) -> bool { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | extern "custom" fn is_even(self) -> bool { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: convert this to an `#[unsafe(naked)]` function | LL + #[unsafe(naked)] -LL | unsafe extern "custom" fn is_even(self) -> bool { +LL | extern "custom" fn is_even(self) -> bool { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:48:5 + --> $DIR/bad-custom.rs:65:5 | -LL | unsafe extern "custom" fn bitwise_not(a: i64) -> i64 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | extern "custom" fn bitwise_not(a: i64) -> i64 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: convert this to an `#[unsafe(naked)]` function | LL + #[unsafe(naked)] -LL | unsafe extern "custom" fn bitwise_not(a: i64) -> i64 { +LL | extern "custom" fn bitwise_not(a: i64) -> i64 { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:64:5 + --> $DIR/bad-custom.rs:82:5 | LL | extern "custom" fn negate(a: i64) -> i64 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -298,96 +396,96 @@ LL | extern "custom" fn negate(a: i64) -> i64 { | error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:82:14 + --> $DIR/bad-custom.rs:100:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:82:14 + --> $DIR/bad-custom.rs:100:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:88:14 + --> $DIR/bad-custom.rs:106:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:88:14 + --> $DIR/bad-custom.rs:106:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:96:14 + --> $DIR/bad-custom.rs:114:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:96:14 + --> $DIR/bad-custom.rs:114:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:118:20 + --> $DIR/bad-custom.rs:136:20 | -LL | assert_eq!(double(21), 42); - | ^^^^^^^^^^ +LL | assert_eq!(not_both(21), 42); + | ^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:118:20 + --> $DIR/bad-custom.rs:136:20 | -LL | assert_eq!(double(21), 42); - | ^^^^^^^^^^ +LL | assert_eq!(not_both(21), 42); + | ^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:121:29 + --> $DIR/bad-custom.rs:139:29 | LL | assert_eq!(unsafe { increment(41) }, 42); | ^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:121:29 + --> $DIR/bad-custom.rs:139:29 | LL | assert_eq!(unsafe { increment(41) }, 42); | ^^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:124:17 + --> $DIR/bad-custom.rs:142:17 | LL | assert!(Thing(41).is_even()); | ^^^^^^^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:124:17 + --> $DIR/bad-custom.rs:142:17 | LL | assert!(Thing(41).is_even()); | ^^^^^^^^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:127:20 + --> $DIR/bad-custom.rs:145:20 | LL | assert_eq!(Thing::bitwise_not(42), !42); | ^^^^^^^^^^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:127:20 + --> $DIR/bad-custom.rs:145:20 | LL | assert_eq!(Thing::bitwise_not(42), !42); | ^^^^^^^^^^^^^^^^^^^^^^ error[E0015]: inline assembly is not allowed in constant functions - --> $DIR/bad-custom.rs:102:5 + --> $DIR/bad-custom.rs:120:5 | LL | std::arch::naked_asm!("") | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 32 previous errors +error: aborting due to 40 previous errors Some errors have detailed explanations: E0015, E0277. For more information about an error, try `rustc --explain E0015`. From d0dde6040c77f86ec06421f6a5e2819c02e06972 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 23 Jul 2026 21:58:54 +0200 Subject: [PATCH 58/61] improve suggestion when `safe` is used on function pointers --- .../rustc_ast_passes/src/ast_validation.rs | 33 +++++++---- compiler/rustc_ast_passes/src/diagnostics.rs | 7 +++ tests/ui/abi/bad-custom.rs | 3 + tests/ui/abi/bad-custom.stderr | 56 +++++++++++-------- .../interrupt-invalid-signature.avr.stderr | 14 ++++- .../interrupt-invalid-signature.i686.stderr | 14 ++++- tests/ui/abi/interrupt-invalid-signature.rs | 8 +++ .../interrupt-invalid-signature.x64.stderr | 14 ++++- tests/ui/rust-2024/safe-outside-extern.stderr | 8 ++- 9 files changed, 121 insertions(+), 36 deletions(-) diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index 1e5fb328950d8..16516347a1f42 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -575,7 +575,7 @@ impl<'a> AstValidator<'a> { CanonAbi::Custom => { // An `extern "custom"` function must be unsafe. - self.reject_safe_fn(abi, ctxt, sig); + self.reject_safe_fn(abi, ctxt, sig, opt_function_name.is_none()); // An `extern "custom"` function cannot be `async` and/or `gen`. self.reject_coroutine(abi, sig); @@ -615,18 +615,27 @@ impl<'a> AstValidator<'a> { } } - fn reject_safe_fn(&self, abi: ExternAbi, ctxt: FnCtxt, sig: &BorrowedFnSig<'_>) { + fn reject_safe_fn( + &self, + abi: ExternAbi, + ctxt: FnCtxt, + sig: &BorrowedFnSig<'_>, + is_fn_ptr: bool, + ) { let dcx = self.dcx(); match sig.header.safety { Safety::Unsafe(_) => { /* all good */ } Safety::Safe(safe_span) => { - let source_map = self.sess.psess.source_map(); - let safe_span = source_map.span_until_non_whitespace(safe_span.to(sig.span)); - dcx.emit_err(diagnostics::AbiCustomSafeForeignFunction { - span: sig.span, - safe_span, - }); + // Function pointers already error when `safe` is used. + if !is_fn_ptr { + let source_map = self.sess.psess.source_map(); + let safe_span = source_map.span_until_non_whitespace(safe_span.to(sig.span)); + dcx.emit_err(diagnostics::AbiCustomSafeForeignFunction { + span: sig.span, + safe_span, + }); + } } Safety::Default => match ctxt { FnCtxt::Foreign => { /* all good */ } @@ -735,8 +744,12 @@ impl<'a> AstValidator<'a> { } fn check_fn_ptr_safety(&self, span: Span, safety: Safety) { - if matches!(safety, Safety::Safe(_)) { - self.dcx().emit_err(diagnostics::InvalidSafetyOnFnPtr { span }); + if let Safety::Safe(safe_span) = safety { + let remove_span = self.sess.source_map().span_until_non_whitespace(span); + self.dcx().emit_err(diagnostics::InvalidSafetyOnFnPtr { + span: safe_span, + safe_span: remove_span, + }); } } diff --git a/compiler/rustc_ast_passes/src/diagnostics.rs b/compiler/rustc_ast_passes/src/diagnostics.rs index 670cb4b66adff..60660dce8e5ea 100644 --- a/compiler/rustc_ast_passes/src/diagnostics.rs +++ b/compiler/rustc_ast_passes/src/diagnostics.rs @@ -371,6 +371,13 @@ pub(crate) struct InvalidSafetyOnItem { pub(crate) struct InvalidSafetyOnFnPtr { #[primary_span] pub span: Span, + #[suggestion( + "remove the `safe` qualifier", + code = "", + applicability = "machine-applicable", + style = "verbose" + )] + pub safe_span: Span, } #[derive(Diagnostic)] diff --git a/tests/ui/abi/bad-custom.rs b/tests/ui/abi/bad-custom.rs index a9e8ae2fd3bf8..793ef4d5dd3ea 100644 --- a/tests/ui/abi/bad-custom.rs +++ b/tests/ui/abi/bad-custom.rs @@ -95,6 +95,9 @@ unsafe extern "custom" { //~^ ERROR foreign functions with the "custom" ABI cannot be safe } +type Safe = safe extern "custom" fn(); +//~^ ERROR function pointers cannot be declared with `safe` safety qualifier + fn caller(f: unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { //~^ ERROR invalid signature for `extern "custom"` function unsafe { f(x) } diff --git a/tests/ui/abi/bad-custom.stderr b/tests/ui/abi/bad-custom.stderr index 1453382285624..8aea96fd7257e 100644 --- a/tests/ui/abi/bad-custom.stderr +++ b/tests/ui/abi/bad-custom.stderr @@ -271,8 +271,20 @@ LL - safe fn extern_cannot_be_safe(); LL + fn extern_cannot_be_safe(); | +error: function pointers cannot be declared with `safe` safety qualifier + --> $DIR/bad-custom.rs:98:13 + | +LL | type Safe = safe extern "custom" fn(); + | ^^^^ + | +help: remove the `safe` qualifier + | +LL - type Safe = safe extern "custom" fn(); +LL + type Safe = extern "custom" fn(); + | + error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:98:40 + --> $DIR/bad-custom.rs:101:40 | LL | fn caller(f: unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { | ^^^ ^^^ @@ -285,7 +297,7 @@ LL + fn caller(f: unsafe extern "custom" fn(), mut x: i64) -> i64 { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:104:48 + --> $DIR/bad-custom.rs:107:48 | LL | fn caller_by_ref(f: &unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 { | ^^^ ^^^ @@ -298,7 +310,7 @@ LL + fn caller_by_ref(f: &unsafe extern "custom" fn(), mut x: i64) -> i64 { | error: invalid signature for `extern "custom"` function - --> $DIR/bad-custom.rs:110:40 + --> $DIR/bad-custom.rs:113:40 | LL | type Alias = unsafe extern "custom" fn(i64) -> i64; | ^^^ ^^^ @@ -311,7 +323,7 @@ LL + type Alias = unsafe extern "custom" fn(); | error: functions with the "custom" ABI cannot be `async` - --> $DIR/bad-custom.rs:124:1 + --> $DIR/bad-custom.rs:127:1 | LL | async unsafe extern "custom" fn no_async_fn() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -323,7 +335,7 @@ LL + unsafe extern "custom" fn no_async_fn() { | error: items with the "custom" ABI can only be declared externally or defined via naked functions - --> $DIR/bad-custom.rs:124:1 + --> $DIR/bad-custom.rs:127:1 | LL | async unsafe extern "custom" fn no_async_fn() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -335,7 +347,7 @@ LL | async unsafe extern "custom" fn no_async_fn() { | error[E0277]: expected an `Fn()` closure, found `unsafe extern "custom" fn()` - --> $DIR/bad-custom.rs:129:64 + --> $DIR/bad-custom.rs:132:64 | LL | fn no_promotion_to_fn_trait(f: unsafe extern "custom" fn()) -> impl Fn() { | ^^^^^^^^^ call the function in a closure: `|| unsafe { /* code */ }` @@ -396,96 +408,96 @@ LL | extern "custom" fn negate(a: i64) -> i64 { | error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:100:14 + --> $DIR/bad-custom.rs:103:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:100:14 + --> $DIR/bad-custom.rs:103:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:106:14 + --> $DIR/bad-custom.rs:109:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:106:14 + --> $DIR/bad-custom.rs:109:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:114:14 + --> $DIR/bad-custom.rs:117:14 | LL | unsafe { f(x) } | ^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:114:14 + --> $DIR/bad-custom.rs:117:14 | LL | unsafe { f(x) } | ^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:136:20 + --> $DIR/bad-custom.rs:139:20 | LL | assert_eq!(not_both(21), 42); | ^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:136:20 + --> $DIR/bad-custom.rs:139:20 | LL | assert_eq!(not_both(21), 42); | ^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:139:29 + --> $DIR/bad-custom.rs:142:29 | LL | assert_eq!(unsafe { increment(41) }, 42); | ^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:139:29 + --> $DIR/bad-custom.rs:142:29 | LL | assert_eq!(unsafe { increment(41) }, 42); | ^^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:142:17 + --> $DIR/bad-custom.rs:145:17 | LL | assert!(Thing(41).is_even()); | ^^^^^^^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:142:17 + --> $DIR/bad-custom.rs:145:17 | LL | assert!(Thing(41).is_even()); | ^^^^^^^^^^^^^^^^^^^ error: functions with the "custom" ABI cannot be called - --> $DIR/bad-custom.rs:145:20 + --> $DIR/bad-custom.rs:148:20 | LL | assert_eq!(Thing::bitwise_not(42), !42); | ^^^^^^^^^^^^^^^^^^^^^^ | note: an `extern "custom"` function can only be called using inline assembly - --> $DIR/bad-custom.rs:145:20 + --> $DIR/bad-custom.rs:148:20 | LL | assert_eq!(Thing::bitwise_not(42), !42); | ^^^^^^^^^^^^^^^^^^^^^^ error[E0015]: inline assembly is not allowed in constant functions - --> $DIR/bad-custom.rs:120:5 + --> $DIR/bad-custom.rs:123:5 | LL | std::arch::naked_asm!("") | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 40 previous errors +error: aborting due to 41 previous errors Some errors have detailed explanations: E0015, E0277. For more information about an error, try `rustc --explain E0015`. diff --git a/tests/ui/abi/interrupt-invalid-signature.avr.stderr b/tests/ui/abi/interrupt-invalid-signature.avr.stderr index 268d593b9120b..7830999260ad6 100644 --- a/tests/ui/abi/interrupt-invalid-signature.avr.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.avr.stderr @@ -37,5 +37,17 @@ LL - fn avr_ptr(_f: extern "avr-interrupt" fn(u8) -> u8) { LL + fn avr_ptr(_f: extern "avr-interrupt" fn()) { | -error: aborting due to 3 previous errors +error: function pointers cannot be declared with `safe` safety qualifier + --> $DIR/interrupt-invalid-signature.rs:141:13 + | +LL | type Safe = safe extern "avr-interrupt" fn(); + | ^^^^ + | +help: remove the `safe` qualifier + | +LL - type Safe = safe extern "avr-interrupt" fn(); +LL + type Safe = extern "avr-interrupt" fn(); + | + +error: aborting due to 4 previous errors diff --git a/tests/ui/abi/interrupt-invalid-signature.i686.stderr b/tests/ui/abi/interrupt-invalid-signature.i686.stderr index 257d7cf84940c..7e28fbf1f3822 100644 --- a/tests/ui/abi/interrupt-invalid-signature.i686.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.i686.stderr @@ -69,5 +69,17 @@ help: remove the return type LL | fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { | ^^ -error: aborting due to 7 previous errors +error: function pointers cannot be declared with `safe` safety qualifier + --> $DIR/interrupt-invalid-signature.rs:145:13 + | +LL | type Safe = safe extern "x86-interrupt" fn(*const u8); + | ^^^^ + | +help: remove the `safe` qualifier + | +LL - type Safe = safe extern "x86-interrupt" fn(*const u8); +LL + type Safe = extern "x86-interrupt" fn(*const u8); + | + +error: aborting due to 8 previous errors diff --git a/tests/ui/abi/interrupt-invalid-signature.rs b/tests/ui/abi/interrupt-invalid-signature.rs index 58aa72de561b5..083d93fef0774 100644 --- a/tests/ui/abi/interrupt-invalid-signature.rs +++ b/tests/ui/abi/interrupt-invalid-signature.rs @@ -136,3 +136,11 @@ fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u //[x64,i686]~^ ERROR invalid signature //[x64,i686]~| ERROR invalid signature } + +#[cfg(avr)] +type Safe = safe extern "avr-interrupt" fn(); +//[avr]~^ ERROR function pointers cannot be declared with `safe` safety qualifier + +#[cfg(any(x64, i686))] +type Safe = safe extern "x86-interrupt" fn(*const u8); +//[x64,i686]~^ ERROR function pointers cannot be declared with `safe` safety qualifier diff --git a/tests/ui/abi/interrupt-invalid-signature.x64.stderr b/tests/ui/abi/interrupt-invalid-signature.x64.stderr index 257d7cf84940c..7e28fbf1f3822 100644 --- a/tests/ui/abi/interrupt-invalid-signature.x64.stderr +++ b/tests/ui/abi/interrupt-invalid-signature.x64.stderr @@ -69,5 +69,17 @@ help: remove the return type LL | fn x86_ptr_too_many(_f: extern "x86-interrupt" fn(*const u8, *const u8, *const u8) -> u8) { | ^^ -error: aborting due to 7 previous errors +error: function pointers cannot be declared with `safe` safety qualifier + --> $DIR/interrupt-invalid-signature.rs:145:13 + | +LL | type Safe = safe extern "x86-interrupt" fn(*const u8); + | ^^^^ + | +help: remove the `safe` qualifier + | +LL - type Safe = safe extern "x86-interrupt" fn(*const u8); +LL + type Safe = extern "x86-interrupt" fn(*const u8); + | + +error: aborting due to 8 previous errors diff --git a/tests/ui/rust-2024/safe-outside-extern.stderr b/tests/ui/rust-2024/safe-outside-extern.stderr index 19d7c5fde0bdf..1e46502df954e 100644 --- a/tests/ui/rust-2024/safe-outside-extern.stderr +++ b/tests/ui/rust-2024/safe-outside-extern.stderr @@ -26,7 +26,13 @@ error: function pointers cannot be declared with `safe` safety qualifier --> $DIR/safe-outside-extern.rs:17:14 | LL | type FnPtr = safe fn(i32, i32) -> i32; - | ^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^ + | +help: remove the `safe` qualifier + | +LL - type FnPtr = safe fn(i32, i32) -> i32; +LL + type FnPtr = fn(i32, i32) -> i32; + | error: static items cannot be declared with `unsafe` safety qualifier outside of `extern` block --> $DIR/safe-outside-extern.rs:20:1 From 99ff7d206cf8f470305d444054981aba98f43c3b Mon Sep 17 00:00:00 2001 From: paradoxicalguy Date: Tue, 21 Jul 2026 17:01:31 +0000 Subject: [PATCH 59/61] bootstrap: use cc::Tool for compiler detection. --- src/bootstrap/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index f08346789e7e8..3087489c7ad57 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -1322,10 +1322,10 @@ impl Build { if let Some(map_to) = self.debuginfo_map_to(which, RemapScheme::NonCompiler) { let map = format!("{}={}", self.src.display(), map_to); - let cc = self.cc(target); - if cc.ends_with("clang") || cc.ends_with("gcc") { + let cc = self.cc_tool(target); + if cc.is_like_clang() || cc.is_like_gnu() { base.push(format!("-fdebug-prefix-map={map}")); - } else if cc.ends_with("clang-cl.exe") { + } else if cc.is_like_clang_cl() { base.push("-Xclang".into()); base.push(format!("-fdebug-prefix-map={map}")); } From 1f8b15fba1f405b680825ce847cf983d0a4eb3b3 Mon Sep 17 00:00:00 2001 From: Joao Roberto Date: Sat, 25 Jul 2026 12:08:44 -0300 Subject: [PATCH 60/61] Fix cfg_attr inner attributes in file modules Return to pretty-printed token reconstruction when lexing source slices around an inner attribute produces unmatched delimiters. Cover cfg_attr-expanded proc-macro attributes in an out-of-line module, including sibling attributes on both sides. --- compiler/rustc_parse/src/lib.rs | 14 +++++++------- .../inner_attr_file_mod_cfg_attr_child.rs | 8 ++++++++ .../ui/proc-macro/inner-attr-file-mod-cfg-attr.rs | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 tests/ui/proc-macro/auxiliary/inner_attr_file_mod_cfg_attr_child.rs create mode 100644 tests/ui/proc-macro/inner-attr-file-mod-cfg-attr.rs diff --git a/compiler/rustc_parse/src/lib.rs b/compiler/rustc_parse/src/lib.rs index cd08fc3dc60a1..539c15f18a9a4 100644 --- a/compiler/rustc_parse/src/lib.rs +++ b/compiler/rustc_parse/src/lib.rs @@ -349,13 +349,13 @@ fn lex_token_trees_for_span( span: Span, ) -> Option> { let src = psess.source_map().span_to_snippet(span).ok()?; - let stream = unwrap_or_emit_fatal(lexer::lex_token_trees( - psess, - &src, - span.lo(), - None, - StripTokens::Nothing, - )); + let stream = match lexer::lex_token_trees(psess, &src, span.lo(), None, StripTokens::Nothing) { + Ok(stream) => stream, + Err(errs) => { + errs.into_iter().for_each(|err| err.cancel()); + return None; + } + }; Some((0..).map_while(move |index| stream.get(index).cloned())) } diff --git a/tests/ui/proc-macro/auxiliary/inner_attr_file_mod_cfg_attr_child.rs b/tests/ui/proc-macro/auxiliary/inner_attr_file_mod_cfg_attr_child.rs new file mode 100644 index 0000000000000..59114fb8e58af --- /dev/null +++ b/tests/ui/proc-macro/auxiliary/inner_attr_file_mod_cfg_attr_child.rs @@ -0,0 +1,8 @@ +#![cfg_attr(enabled, test_macros::identity_attr, allow(dead_code))] +#![cfg_attr(enabled, allow(unused_imports), test_macros::identity_attr)] +#![cfg_attr(enabled, test_macros::identity_attr)] +//! docs + +use std::fmt; + +fn unused() {} diff --git a/tests/ui/proc-macro/inner-attr-file-mod-cfg-attr.rs b/tests/ui/proc-macro/inner-attr-file-mod-cfg-attr.rs new file mode 100644 index 0000000000000..26b6a6f7041a0 --- /dev/null +++ b/tests/ui/proc-macro/inner-attr-file-mod-cfg-attr.rs @@ -0,0 +1,14 @@ +//@ check-pass +//@ proc-macro: test-macros.rs +//@ edition:2024 +//@ compile-flags: --cfg enabled --check-cfg=cfg(enabled) + +#![feature(custom_inner_attributes)] +#![deny(dead_code, unused_attributes, unused_imports)] + +extern crate test_macros; + +#[path = "auxiliary/inner_attr_file_mod_cfg_attr_child.rs"] +mod child; + +fn main() {} From dcc9c46efc68eb50069fd179182b5cf3b1c545a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E6=99=A8=20=28Leo=20Cheng=29?= Date: Sat, 25 Jul 2026 21:25:46 +0800 Subject: [PATCH 61/61] iter: extend step_by specialization to cover StepBy> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 林晨 (Leo Cheng) --- library/core/src/iter/adapters/step_by.rs | 110 +++++++++++++----- library/core/src/range/iter.rs | 2 +- .../coretests/tests/iter/adapters/step_by.rs | 38 ++++++ 3 files changed, 122 insertions(+), 28 deletions(-) diff --git a/library/core/src/iter/adapters/step_by.rs b/library/core/src/iter/adapters/step_by.rs index 2d0f210420317..4c70c9dc84b3b 100644 --- a/library/core/src/iter/adapters/step_by.rs +++ b/library/core/src/iter/adapters/step_by.rs @@ -2,6 +2,7 @@ use crate::intrinsics; use crate::iter::{TrustedLen, TrustedRandomAccess, from_fn}; use crate::num::NonZero; use crate::ops::{Range, Try}; +use crate::range::RangeIter; /// An iterator for stepping iterators by a custom amount. /// @@ -418,37 +419,71 @@ unsafe impl StepByBackImpl for St /// and we must consistently specialize backwards and forwards iteration /// that makes the situation complicated enough that it's not covered /// for now. +/// +/// After `SpecRangeSetup::setup`, both `Range` and its new-range wrapper +/// `RangeIter` carry the cursor and countdown in the same underlying legacy +/// `Range`. This accessor exposes that shared range so one specialization can +/// serve both: it is an identity for `Range` and unwraps the newtype for +/// `RangeIter`, so it compiles away. +trait AsLegacyRange { + fn as_legacy_range(&self) -> &Range; + fn as_legacy_range_mut(&mut self) -> &mut Range; +} + +impl AsLegacyRange for Range { + #[inline] + fn as_legacy_range(&self) -> &Range { + self + } + #[inline] + fn as_legacy_range_mut(&mut self) -> &mut Range { + self + } +} + +impl AsLegacyRange for RangeIter { + #[inline] + fn as_legacy_range(&self) -> &Range { + &self.0 + } + #[inline] + fn as_legacy_range_mut(&mut self) -> &mut Range { + &mut self.0 + } +} + macro_rules! spec_int_ranges { - ($($t:ty)*) => ($( + ($ctor:ident; $($t:ty)*) => ($( const _: () = assert!(usize::BITS >= <$t>::BITS); - impl SpecRangeSetup> for Range<$t> { + impl SpecRangeSetup<$ctor<$t>> for $ctor<$t> { #[inline] - fn setup(mut r: Range<$t>, step: usize) -> Range<$t> { + fn setup(mut r: $ctor<$t>, step: usize) -> $ctor<$t> { let inner_len = r.size_hint().0; // If step exceeds $t::MAX, then the count will be at most 1 and // thus always fit into $t. let yield_count = inner_len.div_ceil(step); // Turn the range end into an iteration counter - r.end = yield_count as $t; + r.as_legacy_range_mut().end = yield_count as $t; r } } - unsafe impl StepByImpl> for StepBy> { + unsafe impl StepByImpl<$ctor<$t>> for StepBy<$ctor<$t>> { #[inline] fn spec_next(&mut self) -> Option<$t> { // if a step size larger than the type has been specified fall back to // t::MAX, in which case remaining will be at most 1. let step = <$t>::try_from(self.original_step().get()).unwrap_or(<$t>::MAX); - let remaining = self.iter.end; + let r = self.iter.as_legacy_range_mut(); + let remaining = r.end; if remaining > 0 { - let val = self.iter.start; + let val = r.start; // this can only overflow during the last step, after which the value // will not be used - self.iter.start = val.wrapping_add(step); - self.iter.end = remaining - 1; + r.start = val.wrapping_add(step); + r.end = remaining - 1; Some(val) } else { None @@ -457,7 +492,7 @@ macro_rules! spec_int_ranges { #[inline] fn spec_size_hint(&self) -> (usize, Option) { - let remaining = self.iter.end as usize; + let remaining = self.iter.as_legacy_range().end as usize; (remaining, Some(remaining)) } @@ -491,9 +526,10 @@ macro_rules! spec_int_ranges { // if a step size larger than the type has been specified fall back to // t::MAX, in which case remaining will be at most 1. let step = <$t>::try_from(self.original_step().get()).unwrap_or(<$t>::MAX); - let remaining = self.iter.end; + let r = self.iter.as_legacy_range(); + let remaining = r.end; let mut acc = init; - let mut val = self.iter.start; + let mut val = r.start; for _ in 0..remaining { acc = f(acc, val); // this can only overflow during the last step, after which the value @@ -507,18 +543,19 @@ macro_rules! spec_int_ranges { } macro_rules! spec_int_ranges_r { - ($($t:ty)*) => ($( + ($ctor:ident; $($t:ty)*) => ($( const _: () = assert!(usize::BITS >= <$t>::BITS); - unsafe impl StepByBackImpl> for StepBy> { + unsafe impl StepByBackImpl<$ctor<$t>> for StepBy<$ctor<$t>> { #[inline] fn spec_next_back(&mut self) -> Option { let step = self.original_step().get() as $t; - let remaining = self.iter.end; + let r = self.iter.as_legacy_range_mut(); + let remaining = r.end; if remaining > 0 { - let start = self.iter.start; - self.iter.end = remaining - 1; + let start = r.start; + r.end = remaining - 1; Some(start + step * (remaining - 1)) } else { None @@ -564,18 +601,37 @@ macro_rules! spec_int_ranges_r { )*) } +// The same specialization covers `Range<{integer}>` and the new-range iterator +// `RangeIter<{integer}>`, which wraps a `Range` (see `AsLegacyRange`). +// +// The backward (`_r`) specialization requires `ExactSizeIterator`. `RangeIter` +// implements it only for `usize`/`u8`/`u16` (see `range_exact_iter_impl!` in +// `range::iter`), narrower than `Range`, so `RangeIter`'s backward set omits +// `u32` even where `Range` includes it; `Range` is likewise omitted on +// 64-bit since its length can exceed `usize`. #[cfg(target_pointer_width = "64")] -spec_int_ranges!(u8 u16 u32 u64 usize); -// DoubleEndedIterator requires ExactSizeIterator, which isn't implemented for Range -#[cfg(target_pointer_width = "64")] -spec_int_ranges_r!(u8 u16 u32 usize); +mod step_by_spec { + use super::*; + spec_int_ranges!(Range; u8 u16 u32 u64 usize); + spec_int_ranges!(RangeIter; u8 u16 u32 u64 usize); + spec_int_ranges_r!(Range; u8 u16 u32 usize); + spec_int_ranges_r!(RangeIter; u8 u16 usize); +} #[cfg(target_pointer_width = "32")] -spec_int_ranges!(u8 u16 u32 usize); -#[cfg(target_pointer_width = "32")] -spec_int_ranges_r!(u8 u16 u32 usize); +mod step_by_spec { + use super::*; + spec_int_ranges!(Range; u8 u16 u32 usize); + spec_int_ranges!(RangeIter; u8 u16 u32 usize); + spec_int_ranges_r!(Range; u8 u16 u32 usize); + spec_int_ranges_r!(RangeIter; u8 u16 usize); +} #[cfg(target_pointer_width = "16")] -spec_int_ranges!(u8 u16 usize); -#[cfg(target_pointer_width = "16")] -spec_int_ranges_r!(u8 u16 usize); +mod step_by_spec { + use super::*; + spec_int_ranges!(Range; u8 u16 usize); + spec_int_ranges!(RangeIter; u8 u16 usize); + spec_int_ranges_r!(Range; u8 u16 usize); + spec_int_ranges_r!(RangeIter; u8 u16 usize); +} diff --git a/library/core/src/range/iter.rs b/library/core/src/range/iter.rs index 01b69554a0b1b..6011949476f6f 100644 --- a/library/core/src/range/iter.rs +++ b/library/core/src/range/iter.rs @@ -8,7 +8,7 @@ use crate::{intrinsics, mem}; /// By-value [`Range`] iterator. #[stable(feature = "new_range_api", since = "1.96.0")] #[derive(Debug, Clone)] -pub struct RangeIter(legacy::Range); +pub struct RangeIter(pub(crate) legacy::Range); impl RangeIter { #[unstable(feature = "new_range_remainder", issue = "154458")] diff --git a/library/coretests/tests/iter/adapters/step_by.rs b/library/coretests/tests/iter/adapters/step_by.rs index 6f3300e7a8820..1ebebb9691933 100644 --- a/library/coretests/tests/iter/adapters/step_by.rs +++ b/library/coretests/tests/iter/adapters/step_by.rs @@ -299,3 +299,41 @@ fn test_step_by_fold_range_specialization() { assert_eq!(r.sum::(), usize::MAX - 1); }); } + +#[test] +fn test_step_by_new_range_iter() { + use core::range::Range as NewRange; + + // forward iteration + let v: Vec = NewRange::from(0_u32..10).into_iter().step_by(3).collect(); + assert_eq!(v, [0, 3, 6, 9]); + + // size_hint + assert_eq!(NewRange::from(0_u32..10).into_iter().step_by(3).size_hint(), (4, Some(4))); + assert_eq!(NewRange::from(0_u32..9).into_iter().step_by(3).size_hint(), (3, Some(3))); + + // nth + assert_eq!(NewRange::from(0_u32..20).into_iter().step_by(5).nth(2), Some(10)); + + // empty range + assert_eq!(NewRange::from(5_u32..5).into_iter().step_by(1).next(), None); + + // step larger than range + assert_eq!(NewRange::from(0_u32..3).into_iter().step_by(10).collect::>(), [0]); + + // backward iteration (usize has ExactSizeIterator) + let mut it = NewRange::from(0_usize..11).into_iter().step_by(3); + assert_eq!(it.next_back(), Some(9)); + assert_eq!(it.next_back(), Some(6)); + assert_eq!(it.next_back(), Some(3)); + assert_eq!(it.next_back(), Some(0)); + assert_eq!(it.next_back(), None); + + // interleaved forward and backward + let mut it = NewRange::from(0_usize..16).into_iter().step_by(5); + assert_eq!(it.next(), Some(0)); + assert_eq!(it.next_back(), Some(15)); + assert_eq!(it.next(), Some(5)); + assert_eq!(it.next_back(), Some(10)); + assert_eq!(it.next(), None); +}