Port #[custom_mir(..)] to the new attribute system

This commit is contained in:
Sasha Pourcelot
2025-08-10 09:44:39 +02:00
parent 3507a749b3
commit 51bccdd1ab
15 changed files with 318 additions and 91 deletions

View File

@@ -43,6 +43,7 @@ pub(crate) mod no_implicit_prelude;
pub(crate) mod non_exhaustive;
pub(crate) mod path;
pub(crate) mod proc_macro_attrs;
pub(crate) mod prototype;
pub(crate) mod repr;
pub(crate) mod rustc_internal;
pub(crate) mod semantics;

View File

@@ -0,0 +1,140 @@
//! Attributes that are only used on function prototypes.
use rustc_feature::{AttributeTemplate, template};
use rustc_hir::Target;
use rustc_hir::attrs::{AttributeKind, MirDialect, MirPhase};
use rustc_span::{Span, Symbol, sym};
use super::{AttributeOrder, OnDuplicate};
use crate::attributes::SingleAttributeParser;
use crate::context::{AcceptContext, AllowedTargets, MaybeWarn, Stage};
use crate::parser::ArgParser;
pub(crate) struct CustomMirParser;
impl<S: Stage> SingleAttributeParser<S> for CustomMirParser {
const PATH: &[rustc_span::Symbol] = &[sym::custom_mir];
const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
const ALLOWED_TARGETS: AllowedTargets =
AllowedTargets::AllowList(&[MaybeWarn::Allow(Target::Fn)]);
const TEMPLATE: AttributeTemplate = template!(List: &[r#"dialect = "...", phase = "...""#]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
let Some(list) = args.list() else {
cx.expected_list(cx.attr_span);
return None;
};
let mut dialect = None;
let mut phase = None;
let mut failed = false;
for item in list.mixed() {
let Some(meta_item) = item.meta_item() else {
cx.expected_name_value(item.span(), None);
failed = true;
break;
};
if let Some(arg) = meta_item.word_is(sym::dialect) {
extract_value(cx, sym::dialect, arg, meta_item.span(), &mut dialect, &mut failed);
} else if let Some(arg) = meta_item.word_is(sym::phase) {
extract_value(cx, sym::phase, arg, meta_item.span(), &mut phase, &mut failed);
} else if let Some(word) = meta_item.path().word() {
let word = word.to_string();
cx.unknown_key(meta_item.span(), word, &["dialect", "phase"]);
failed = true;
} else {
cx.expected_name_value(meta_item.span(), None);
failed = true;
};
}
let dialect = parse_dialect(cx, dialect, &mut failed);
let phase = parse_phase(cx, phase, &mut failed);
if failed {
return None;
}
Some(AttributeKind::CustomMir(dialect, phase, cx.attr_span))
}
}
fn extract_value<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
key: Symbol,
arg: &ArgParser<'_>,
span: Span,
out_val: &mut Option<(Symbol, Span)>,
failed: &mut bool,
) {
if out_val.is_some() {
cx.duplicate_key(span, key);
*failed = true;
return;
}
let Some(val) = arg.name_value() else {
cx.expected_single_argument(arg.span().unwrap_or(span));
*failed = true;
return;
};
let Some(value_sym) = val.value_as_str() else {
cx.expected_string_literal(val.value_span, Some(val.value_as_lit()));
*failed = true;
return;
};
*out_val = Some((value_sym, val.value_span));
}
fn parse_dialect<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
dialect: Option<(Symbol, Span)>,
failed: &mut bool,
) -> Option<(MirDialect, Span)> {
let (dialect, span) = dialect?;
let dialect = match dialect {
sym::analysis => MirDialect::Analysis,
sym::built => MirDialect::Built,
sym::runtime => MirDialect::Runtime,
_ => {
cx.expected_specific_argument(span, vec!["analysis", "built", "runtime"]);
*failed = true;
return None;
}
};
Some((dialect, span))
}
fn parse_phase<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
phase: Option<(Symbol, Span)>,
failed: &mut bool,
) -> Option<(MirPhase, Span)> {
let (phase, span) = phase?;
let phase = match phase {
sym::initial => MirPhase::Initial,
sym::post_cleanup => MirPhase::PostCleanup,
sym::optimized => MirPhase::Optimized,
_ => {
cx.expected_specific_argument(span, vec!["initial", "post-cleanup", "optimized"]);
*failed = true;
return None;
}
};
Some((phase, span))
}

View File

@@ -46,6 +46,7 @@ use crate::attributes::path::PathParser as PathAttributeParser;
use crate::attributes::proc_macro_attrs::{
ProcMacroAttributeParser, ProcMacroDeriveParser, ProcMacroParser, RustcBuiltinMacroParser,
};
use crate::attributes::prototype::CustomMirParser;
use crate::attributes::repr::{AlignParser, ReprParser};
use crate::attributes::rustc_internal::{
RustcLayoutScalarValidRangeEnd, RustcLayoutScalarValidRangeStart,
@@ -167,6 +168,7 @@ attribute_parsers!(
// tidy-alphabetical-start
Single<CoverageParser>,
Single<CustomMirParser>,
Single<DeprecationParser>,
Single<DummyParser>,
Single<ExportNameParser>,

View File

@@ -9,6 +9,7 @@ use rustc_abi::TargetDataLayoutErrors;
use rustc_ast::util::parser::ExprPrecedence;
use rustc_ast_pretty::pprust;
use rustc_hir::RustcVersion;
use rustc_hir::attrs::{MirDialect, MirPhase};
use rustc_macros::Subdiagnostic;
use rustc_span::edition::Edition;
use rustc_span::{Ident, MacroRulesNormalizedIdent, Span, Symbol};
@@ -312,6 +313,28 @@ impl IntoDiagArg for ExprPrecedence {
}
}
impl IntoDiagArg for MirDialect {
fn into_diag_arg(self, _path: &mut Option<PathBuf>) -> DiagArgValue {
let arg = match self {
MirDialect::Analysis => "analysis",
MirDialect::Built => "built",
MirDialect::Runtime => "runtime",
};
DiagArgValue::Str(Cow::Borrowed(arg))
}
}
impl IntoDiagArg for MirPhase {
fn into_diag_arg(self, _path: &mut Option<PathBuf>) -> DiagArgValue {
let arg = match self {
MirPhase::Initial => "initial",
MirPhase::PostCleanup => "post-cleanup",
MirPhase::Optimized => "optimized",
};
DiagArgValue::Str(Cow::Borrowed(arg))
}
}
#[derive(Clone)]
pub struct DiagSymbolList<S = Symbol>(Vec<S>);

View File

@@ -205,6 +205,22 @@ pub enum Linkage {
WeakODR,
}
#[derive(Clone, Copy, Decodable, Debug, Encodable, PartialEq)]
#[derive(HashStable_Generic, PrintAttribute)]
pub enum MirDialect {
Analysis,
Built,
Runtime,
}
#[derive(Clone, Copy, Decodable, Debug, Encodable, PartialEq)]
#[derive(HashStable_Generic, PrintAttribute)]
pub enum MirPhase {
Initial,
PostCleanup,
Optimized,
}
/// Represents parsed *built-in* inert attributes.
///
/// ## Overview
@@ -324,6 +340,9 @@ pub enum AttributeKind {
/// Represents `#[coverage(..)]`.
Coverage(Span, CoverageAttrKind),
/// Represents `#[custom_mir]`.
CustomMir(Option<(MirDialect, Span)>, Option<(MirPhase, Span)>, Span),
///Represents `#[rustc_deny_explicit_impl]`.
DenyExplicitImpl(Span),

View File

@@ -31,6 +31,7 @@ impl AttributeKind {
ConstTrait(..) => No,
Coroutine(..) => No,
Coverage(..) => No,
CustomMir(_, _, _) => Yes,
DenyExplicitImpl(..) => No,
Deprecation { .. } => Yes,
DoNotImplementViaObject(..) => No,

View File

@@ -115,48 +115,6 @@ impl MirPhase {
MirPhase::Runtime(runtime_phase) => (3, 1 + runtime_phase as usize),
}
}
/// Parses a `MirPhase` from a pair of strings. Panics if this isn't possible for any reason.
pub fn parse(dialect: String, phase: Option<String>) -> Self {
match &*dialect.to_ascii_lowercase() {
"built" => {
assert!(phase.is_none(), "Cannot specify a phase for `Built` MIR");
MirPhase::Built
}
"analysis" => Self::Analysis(AnalysisPhase::parse(phase)),
"runtime" => Self::Runtime(RuntimePhase::parse(phase)),
_ => bug!("Unknown MIR dialect: '{}'", dialect),
}
}
}
impl AnalysisPhase {
pub fn parse(phase: Option<String>) -> Self {
let Some(phase) = phase else {
return Self::Initial;
};
match &*phase.to_ascii_lowercase() {
"initial" => Self::Initial,
"post_cleanup" | "post-cleanup" | "postcleanup" => Self::PostCleanup,
_ => bug!("Unknown analysis phase: '{}'", phase),
}
}
}
impl RuntimePhase {
pub fn parse(phase: Option<String>) -> Self {
let Some(phase) = phase else {
return Self::Initial;
};
match &*phase.to_ascii_lowercase() {
"initial" => Self::Initial,
"post_cleanup" | "post-cleanup" | "postcleanup" => Self::PostCleanup,
"optimized" => Self::Optimized,
_ => bug!("Unknown runtime phase: '{}'", phase),
}
}
}
/// Where a specific `mir::Body` comes from.

View File

@@ -19,10 +19,10 @@
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def_id::DefId;
use rustc_hir::{Attribute, HirId};
use rustc_hir::{HirId, attrs};
use rustc_index::{IndexSlice, IndexVec};
use rustc_middle::bug;
use rustc_middle::mir::*;
use rustc_middle::span_bug;
use rustc_middle::thir::*;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_span::Span;
@@ -39,7 +39,8 @@ pub(super) fn build_custom_mir<'tcx>(
return_ty: Ty<'tcx>,
return_ty_span: Span,
span: Span,
attr: &Attribute,
dialect: Option<attrs::MirDialect>,
phase: Option<attrs::MirPhase>,
) -> Body<'tcx> {
let mut body = Body {
basic_blocks: BasicBlocks::new(IndexVec::new()),
@@ -72,7 +73,7 @@ pub(super) fn build_custom_mir<'tcx>(
inlined_parent_scope: None,
local_data: ClearCrossCrate::Set(SourceScopeLocalData { lint_root: hir_id }),
});
body.injection_phase = Some(parse_attribute(attr));
body.injection_phase = Some(parse_attribute(dialect, phase));
let mut pctxt = ParseCtxt {
tcx,
@@ -98,40 +99,38 @@ pub(super) fn build_custom_mir<'tcx>(
body
}
fn parse_attribute(attr: &Attribute) -> MirPhase {
let meta_items = attr.meta_item_list().unwrap();
let mut dialect: Option<String> = None;
let mut phase: Option<String> = None;
// Not handling errors properly for this internal attribute; will just abort on errors.
for nested in meta_items {
let name = nested.name().unwrap();
let value = nested.value_str().unwrap().as_str().to_string();
match name.as_str() {
"dialect" => {
assert!(dialect.is_none());
dialect = Some(value);
}
"phase" => {
assert!(phase.is_none());
phase = Some(value);
}
other => {
span_bug!(
nested.span(),
"Unexpected key while parsing custom_mir attribute: '{}'",
other
);
}
}
}
/// Turns the arguments passed to `#[custom_mir(..)]` into a proper
/// [`MirPhase`]. Panics if this isn't possible for any reason.
fn parse_attribute(dialect: Option<attrs::MirDialect>, phase: Option<attrs::MirPhase>) -> MirPhase {
let Some(dialect) = dialect else {
// Caught during attribute checking.
assert!(phase.is_none());
return MirPhase::Built;
};
MirPhase::parse(dialect, phase)
match dialect {
attrs::MirDialect::Built => {
// Caught during attribute checking.
assert!(phase.is_none(), "Cannot specify a phase for `Built` MIR");
MirPhase::Built
}
attrs::MirDialect::Analysis => match phase {
None | Some(attrs::MirPhase::Initial) => MirPhase::Analysis(AnalysisPhase::Initial),
Some(attrs::MirPhase::PostCleanup) => MirPhase::Analysis(AnalysisPhase::PostCleanup),
Some(attrs::MirPhase::Optimized) => {
// Caught during attribute checking.
bug!("`optimized` dialect is not compatible with the `analysis` dialect")
}
},
attrs::MirDialect::Runtime => match phase {
None | Some(attrs::MirPhase::Initial) => MirPhase::Runtime(RuntimePhase::Initial),
Some(attrs::MirPhase::PostCleanup) => MirPhase::Runtime(RuntimePhase::PostCleanup),
Some(attrs::MirPhase::Optimized) => MirPhase::Runtime(RuntimePhase::Optimized),
},
}
}
struct ParseCtxt<'a, 'tcx> {

View File

@@ -11,9 +11,10 @@ use rustc_ast::attr;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::sorted_map::SortedIndexMultiMap;
use rustc_errors::ErrorGuaranteed;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{self as hir, BindingMode, ByRef, HirId, ItemLocalId, Node};
use rustc_hir::{self as hir, BindingMode, ByRef, HirId, ItemLocalId, Node, find_attr};
use rustc_index::bit_set::GrowableBitSet;
use rustc_index::{Idx, IndexSlice, IndexVec};
use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
@@ -479,8 +480,7 @@ fn construct_fn<'tcx>(
ty => span_bug!(span_with_body, "unexpected type of body: {ty:?}"),
};
if let Some(custom_mir_attr) =
tcx.hir_attrs(fn_id).iter().find(|attr| attr.has_name(sym::custom_mir))
if let Some((dialect, phase)) = find_attr!(tcx.hir_attrs(fn_id), AttributeKind::CustomMir(dialect, phase, _) => (dialect, phase))
{
return custom::build_custom_mir(
tcx,
@@ -492,7 +492,8 @@ fn construct_fn<'tcx>(
return_ty,
return_ty_span,
span_with_body,
custom_mir_attr,
dialect.as_ref().map(|(d, _)| *d),
phase.as_ref().map(|(p, _)| *p),
);
}

View File

@@ -5,8 +5,9 @@ use std::ops::Bound;
use rustc_ast::AsmMacro;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_errors::DiagArgValue;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::DefKind;
use rustc_hir::{self as hir, BindingMode, ByRef, HirId, Mutability};
use rustc_hir::{self as hir, BindingMode, ByRef, HirId, Mutability, find_attr};
use rustc_middle::middle::codegen_fn_attrs::TargetFeature;
use rustc_middle::mir::BorrowKind;
use rustc_middle::span_bug;
@@ -1157,7 +1158,7 @@ pub(crate) fn check_unsafety(tcx: TyCtxt<'_>, def: LocalDefId) {
// Closures and inline consts are handled by their owner, if it has a body
assert!(!tcx.is_typeck_child(def.to_def_id()));
// Also, don't safety check custom MIR
if tcx.has_attr(def, sym::custom_mir) {
if find_attr!(tcx.get_all_attrs(def), AttributeKind::CustomMir(..) => ()).is_some() {
return;
}

View File

@@ -4,11 +4,11 @@
use rustc_data_structures::steal::Steal;
use rustc_errors::ErrorGuaranteed;
use rustc_hir as hir;
use rustc_hir::HirId;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::lang_items::LangItem;
use rustc_hir::{self as hir, HirId, find_attr};
use rustc_middle::bug;
use rustc_middle::middle::region;
use rustc_middle::thir::*;
@@ -111,10 +111,8 @@ impl<'tcx> ThirBuildCx<'tcx> {
typeck_results,
rvalue_scopes: &typeck_results.rvalue_scopes,
body_owner: def.to_def_id(),
apply_adjustments: tcx
.hir_attrs(hir_id)
.iter()
.all(|attr| !attr.has_name(rustc_span::sym::custom_mir)),
apply_adjustments:
!find_attr!(tcx.hir_attrs(hir_id), AttributeKind::CustomMir(..) => ()).is_some(),
}
}

View File

@@ -74,6 +74,16 @@ passes_const_stable_not_stable =
attribute `#[rustc_const_stable]` can only be applied to functions that are declared `#[stable]`
.label = attribute specified here
passes_custom_mir_incompatible_dialect_and_phase =
The {$dialect} dialect is not compatible with the {$phase} phase
.dialect_span = this dialect...
.phase_span = ... is not compatible with this phase
passes_custom_mir_phase_requires_dialect =
`dialect` key required
.phase_span = `phase` argument requires a `dialect` argument
passes_dead_codes =
{ $multiple ->
*[true] multiple {$descr}s are

View File

@@ -18,7 +18,7 @@ use rustc_feature::{
ACCEPTED_LANG_FEATURES, AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP,
BuiltinAttribute,
};
use rustc_hir::attrs::{AttributeKind, InlineAttr, ReprAttr};
use rustc_hir::attrs::{AttributeKind, InlineAttr, MirDialect, MirPhase, ReprAttr};
use rustc_hir::def::DefKind;
use rustc_hir::def_id::LocalModDefId;
use rustc_hir::intravisit::{self, Visitor};
@@ -197,6 +197,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
Attribute::Parsed(AttributeKind::MustUse { span, .. }) => {
self.check_must_use(hir_id, *span, target)
}
&Attribute::Parsed(AttributeKind::CustomMir(dialect, phase, attr_span)) => {
self.check_custom_mir(dialect, phase, attr_span)
}
Attribute::Parsed(
AttributeKind::BodyStability { .. }
| AttributeKind::ConstStabilityIndirect
@@ -248,7 +251,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| AttributeKind::Coroutine(..)
| AttributeKind::Linkage(..),
) => { /* do nothing */ }
Attribute::Unparsed(attr_item) => {
style = Some(attr_item.style);
match attr.path().as_slice() {
@@ -331,8 +333,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| sym::panic_handler
| sym::lang
| sym::needs_allocator
| sym::default_lib_allocator
| sym::custom_mir,
| sym::default_lib_allocator,
..
] => {}
[name, rest@..] => {
@@ -2113,6 +2114,48 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
self.dcx().emit_err(errors::ConstContinueAttr { attr_span, node_span });
};
}
fn check_custom_mir(
&self,
dialect: Option<(MirDialect, Span)>,
phase: Option<(MirPhase, Span)>,
attr_span: Span,
) {
let Some((dialect, dialect_span)) = dialect else {
if let Some((_, phase_span)) = phase {
self.dcx()
.emit_err(errors::CustomMirPhaseRequiresDialect { attr_span, phase_span });
}
return;
};
match dialect {
MirDialect::Analysis => {
if let Some((MirPhase::Optimized, phase_span)) = phase {
self.dcx().emit_err(errors::CustomMirIncompatibleDialectAndPhase {
dialect,
phase: MirPhase::Optimized,
attr_span,
dialect_span,
phase_span,
});
}
}
MirDialect::Built => {
if let Some((phase, phase_span)) = phase {
self.dcx().emit_err(errors::CustomMirIncompatibleDialectAndPhase {
dialect,
phase,
attr_span,
dialect_span,
phase_span,
});
}
}
MirDialect::Runtime => {}
}
}
}
impl<'tcx> Visitor<'tcx> for CheckAttrVisitor<'tcx> {

View File

@@ -7,6 +7,7 @@ use rustc_errors::{
MultiSpan, Subdiagnostic,
};
use rustc_hir::Target;
use rustc_hir::attrs::{MirDialect, MirPhase};
use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
use rustc_middle::ty::{MainDefinition, Ty};
use rustc_span::{DUMMY_SP, Span, Symbol};
@@ -1570,3 +1571,25 @@ pub(crate) struct ReprAlignShouldBeAlign {
pub span: Span,
pub item: &'static str,
}
#[derive(Diagnostic)]
#[diag(passes_custom_mir_phase_requires_dialect)]
pub(crate) struct CustomMirPhaseRequiresDialect {
#[primary_span]
pub attr_span: Span,
#[label]
pub phase_span: Span,
}
#[derive(Diagnostic)]
#[diag(passes_custom_mir_incompatible_dialect_and_phase)]
pub(crate) struct CustomMirIncompatibleDialectAndPhase {
pub dialect: MirDialect,
pub phase: MirPhase,
#[primary_span]
pub attr_span: Span,
#[label]
pub dialect_span: Span,
#[label]
pub phase_span: Span,
}

View File

@@ -446,6 +446,7 @@ symbols! {
altivec,
alu32,
always,
analysis,
and,
and_then,
anon,
@@ -587,6 +588,7 @@ symbols! {
btreemap_contains_key,
btreemap_insert,
btreeset_iter,
built,
builtin_syntax,
c,
c_dash_variadic,
@@ -851,6 +853,7 @@ symbols! {
destructuring_assignment,
diagnostic,
diagnostic_namespace,
dialect,
direct,
discriminant_kind,
discriminant_type,
@@ -1207,6 +1210,7 @@ symbols! {
infer_static_outlives_requirements,
inherent_associated_types,
inherit,
initial,
inlateout,
inline,
inline_const,
@@ -1542,6 +1546,7 @@ symbols! {
opt_out_copy,
optimize,
optimize_attribute,
optimized,
optin_builtin_traits,
option,
option_env,
@@ -1623,6 +1628,7 @@ symbols! {
pattern_types,
permissions_from_mode,
phantom_data,
phase,
pic,
pie,
pin,
@@ -1639,6 +1645,7 @@ symbols! {
poll,
poll_next,
position,
post_cleanup: "post-cleanup",
post_dash_lto: "post-lto",
postfix_match,
powerpc_target_feature,
@@ -1802,6 +1809,7 @@ symbols! {
roundf128,
rt,
rtm_target_feature,
runtime,
rust,
rust_2015,
rust_2018,