support #[cfg(...)] on arguments to the asm! macros

This commit is contained in:
Folkert de Vries 2025-04-30 11:23:09 +02:00
parent 6eef33bb39
commit e3bbbeeafd
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
10 changed files with 421 additions and 4 deletions

View file

@ -1,6 +1,11 @@
builtin_macros_alloc_error_must_be_fn = alloc_error_handler must be a function
builtin_macros_alloc_must_statics = allocators must be statics
builtin_macros_asm_attribute_not_supported =
this attribute is not supported on assembly
builtin_macros_asm_cfg =
the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable
builtin_macros_asm_clobber_abi = clobber_abi
builtin_macros_asm_clobber_no_reg = asm with `clobber_abi` must specify explicit registers for outputs
builtin_macros_asm_clobber_outputs = generic outputs

View file

@ -10,18 +10,20 @@ use rustc_index::bit_set::GrowableBitSet;
use rustc_parse::exp;
use rustc_parse::parser::{ExpKeywordPair, Parser};
use rustc_session::lint;
use rustc_span::{ErrorGuaranteed, InnerSpan, Span, Symbol, kw};
use rustc_session::parse::feature_err;
use rustc_span::{ErrorGuaranteed, InnerSpan, Span, Symbol, kw, sym};
use rustc_target::asm::InlineAsmArch;
use smallvec::smallvec;
use {rustc_ast as ast, rustc_parse_format as parse};
use crate::errors;
use crate::util::{ExprToSpannedString, expr_to_spanned_string};
use crate::{errors, fluent_generated as fluent};
/// An argument to one of the `asm!` macros. The argument is syntactically valid, but is otherwise
/// not validated at all.
pub struct AsmArg {
pub kind: AsmArgKind,
pub attributes: AsmAttrVec,
pub span: Span,
}
@ -52,6 +54,44 @@ struct ValidatedAsmArgs {
pub options_spans: Vec<Span>,
}
/// A parsed list of attributes that is not attached to any item.
/// Used to check whether `asm!` arguments are configured out.
pub struct AsmAttrVec(pub ast::AttrVec);
impl AsmAttrVec {
fn parse<'a>(p: &mut Parser<'a>) -> PResult<'a, Self> {
let mut attributes = ast::AttrVec::new();
while p.token == token::Pound {
let attr = p.parse_attribute(rustc_parse::parser::attr::InnerAttrPolicy::Permitted)?;
attributes.push(attr);
}
Ok(Self(attributes))
}
}
impl ast::HasAttrs for AsmAttrVec {
// Follows `ast::Expr`.
const SUPPORTS_CUSTOM_INNER_ATTRS: bool = false;
fn attrs(&self) -> &[rustc_ast::Attribute] {
&self.0
}
fn visit_attrs(&mut self, f: impl FnOnce(&mut rustc_ast::AttrVec)) {
f(&mut self.0)
}
}
impl ast::HasTokens for AsmAttrVec {
fn tokens(&self) -> Option<&rustc_ast::tokenstream::LazyAttrTokenStream> {
None
}
fn tokens_mut(&mut self) -> Option<&mut Option<rustc_ast::tokenstream::LazyAttrTokenStream>> {
None
}
}
/// Used for better error messages when operand types are used that are not
/// supported by the current macro (e.g. `in` or `out` for `global_asm!`)
///
@ -167,8 +207,13 @@ pub fn parse_asm_args<'a>(
let mut args = Vec::new();
let attributes = AsmAttrVec::parse(p)?;
let first_template = p.parse_expr()?;
args.push(AsmArg { span: first_template.span, kind: AsmArgKind::Template(first_template) });
args.push(AsmArg {
span: first_template.span,
kind: AsmArgKind::Template(first_template),
attributes,
});
let mut allow_templates = true;
@ -188,6 +233,7 @@ pub fn parse_asm_args<'a>(
break;
}
let attributes = AsmAttrVec::parse(p)?;
let span_start = p.token.span;
// Parse `clobber_abi`.
@ -197,6 +243,7 @@ pub fn parse_asm_args<'a>(
args.push(AsmArg {
kind: AsmArgKind::ClobberAbi(parse_clobber_abi(p)?),
span: span_start.to(p.prev_token.span),
attributes,
});
continue;
@ -209,6 +256,7 @@ pub fn parse_asm_args<'a>(
args.push(AsmArg {
kind: AsmArgKind::Options(parse_options(p, asm_macro)?),
span: span_start.to(p.prev_token.span),
attributes,
});
continue;
@ -231,6 +279,7 @@ pub fn parse_asm_args<'a>(
args.push(AsmArg {
span: span_start.to(p.prev_token.span),
kind: AsmArgKind::Operand(name, op),
attributes,
});
} else if allow_templates {
let template = p.parse_expr()?;
@ -252,7 +301,11 @@ pub fn parse_asm_args<'a>(
}
}
args.push(AsmArg { span: template.span, kind: AsmArgKind::Template(template) });
args.push(AsmArg {
span: template.span,
kind: AsmArgKind::Template(template),
attributes,
});
} else {
p.unexpected_any()?
}
@ -278,6 +331,13 @@ fn validate_asm_args<'a>(
) -> PResult<'a, ValidatedAsmArgs> {
let dcx = ecx.dcx();
let strip_unconfigured = rustc_expand::config::StripUnconfigured {
sess: ecx.sess,
features: Some(ecx.ecfg.features),
config_tokens: false,
lint_node_id: ecx.current_expansion.lint_node_id,
};
let mut validated = ValidatedAsmArgs {
templates: vec![],
operands: vec![],
@ -291,6 +351,26 @@ fn validate_asm_args<'a>(
let mut allow_templates = true;
for arg in args {
for attr in arg.attributes.0.iter() {
match attr.name() {
Some(sym::cfg | sym::cfg_attr) => {
if !ecx.ecfg.features.asm_cfg() {
let span = attr.span();
feature_err(ecx.sess, sym::asm_cfg, span, fluent::builtin_macros_asm_cfg)
.emit();
}
}
_ => {
ecx.dcx().emit_err(errors::AsmAttributeNotSupported { span: attr.span() });
}
}
}
// Skip arguments that are configured out.
if ecx.ecfg.features.asm_cfg() && strip_unconfigured.configure(arg.attributes).is_none() {
continue;
}
match arg.kind {
AsmArgKind::Template(template) => {
// The error for the first template is delayed.

View file

@ -795,6 +795,13 @@ pub(crate) struct AsmRequiresTemplate {
pub(crate) span: Span,
}
#[derive(Diagnostic)]
#[diag(builtin_macros_asm_attribute_not_supported)]
pub(crate) struct AsmAttributeNotSupported {
#[primary_span]
pub(crate) span: Span,
}
#[derive(Diagnostic)]
#[diag(builtin_macros_asm_expected_comma)]
pub(crate) struct AsmExpectedComma {

View file

@ -371,6 +371,8 @@ declare_features! (
(unstable, arbitrary_self_types, "1.23.0", Some(44874)),
/// Allows inherent and trait methods with arbitrary self types that are raw pointers.
(unstable, arbitrary_self_types_pointers, "1.83.0", Some(44874)),
/// Allows #[cfg(...)] on inline assembly templates and operands.
(unstable, asm_cfg, "CURRENT_RUSTC_VERSION", Some(140364)),
/// Enables experimental inline assembly support for additional architectures.
(unstable, asm_experimental_arch, "1.58.0", Some(93335)),
/// Enables experimental register support in inline assembly.

View file

@ -475,6 +475,7 @@ symbols! {
as_ref,
as_str,
asm,
asm_cfg,
asm_const,
asm_experimental_arch,
asm_experimental_reg,

View file

@ -0,0 +1,56 @@
//@ needs-asm-support
#![feature(asm_cfg)]
use std::arch::asm;
fn main() {
unsafe {
asm!(
"",
#[cfg(false)]
clobber_abi("C"),
#[cfg(false)]
options(att_syntax),
#[cfg(false)]
a = out(reg) x,
"",
//~^ ERROR expected one of `clobber_abi`, `const`
);
asm!(
#[cfg(false)]
"",
#[cfg(false)]
const {
5
},
"", //~ ERROR expected one of `clobber_abi`, `const`
);
asm!(
#[cfg_attr(true, cfg(false))]
const {
5
},
"",
);
// This is not accepted because `a = out(reg) x` is not a valid expression.
asm!(
#[cfg(false)]
a = out(reg) x, //~ ERROR expected token: `,`
"",
);
// For now, any non-cfg attributes are rejected
asm!(
#[rustfmt::skip] //~ ERROR this attribute is not supported on assembly
"",
);
// For now, any non-cfg attributes are rejected
asm!(
#![rustfmt::skip] //~ ERROR an inner attribute is not permitted in this context
"",
);
}
}

View file

@ -0,0 +1,36 @@
error: expected one of `clobber_abi`, `const`, `in`, `inlateout`, `inout`, `label`, `lateout`, `options`, `out`, or `sym`, found `""`
--> $DIR/cfg-parse-error.rs:16:13
|
LL | a = out(reg) x,
| - expected one of 10 possible tokens
LL | "",
| ^^ unexpected token
error: expected one of `clobber_abi`, `const`, `in`, `inlateout`, `inout`, `label`, `lateout`, `options`, `out`, or `sym`, found `""`
--> $DIR/cfg-parse-error.rs:26:13
|
LL | },
| - expected one of 10 possible tokens
LL | "",
| ^^ unexpected token
error: expected token: `,`
--> $DIR/cfg-parse-error.rs:40:26
|
LL | a = out(reg) x,
| ^ expected `,`
error: this attribute is not supported on assembly
--> $DIR/cfg-parse-error.rs:46:13
|
LL | #[rustfmt::skip]
| ^^^^^^^^^^^^^^^^
error: this attribute is not supported on assembly
--> $DIR/cfg-parse-error.rs:52:13
|
LL | #![rustfmt::skip]
| ^^^^^^^^^^^^^^^^^
error: aborting due to 5 previous errors

125
tests/ui/asm/cfg.rs Normal file
View file

@ -0,0 +1,125 @@
// Check that `cfg` and `cfg_attr` work as expected.
//
//@ revisions: reva revb
//@ only-x86_64
//@ run-pass
#![feature(asm_cfg, cfg_select)]
use std::arch::{asm, naked_asm};
#[unsafe(naked)]
extern "C" fn ignore_const_operand() -> u64 {
naked_asm!(
"mov rax, 5",
#[cfg(revb)]
"mov rax, {a}",
"ret",
#[cfg(revb)]
a = const 10,
)
}
#[unsafe(naked)]
extern "C" fn ignore_const_operand_cfg_attr() -> u64 {
naked_asm!(
"mov rax, 5",
#[cfg_attr(true, cfg(revb))]
"mov rax, {a}",
"ret",
#[cfg_attr(true, cfg(revb))]
a = const 10,
)
}
#[unsafe(naked)]
extern "C" fn const_operand() -> u64 {
naked_asm!(
"mov rax, {a}",
"ret",
#[cfg(reva)]
a = const 5,
#[cfg(revb)]
a = const 10,
)
}
fn options() {
// Without the cfg, this throws an error that the `att_syntax` option is provided twice.
unsafe {
asm!(
"nop",
#[cfg(false)]
options(att_syntax),
options(att_syntax)
)
}
}
fn clobber_abi() {
// Without the cfg, this throws an error that the "C" abi is provided twice.
unsafe {
asm!(
"nop",
#[cfg(false)]
clobber_abi("C"),
clobber_abi("C"),
);
}
}
#[unsafe(naked)]
extern "C" fn first_template() -> u64 {
naked_asm!(
#[cfg(reva)]
"mov rax, 5",
#[cfg(revb)]
"mov rax, 10",
"ret",
)
}
#[unsafe(naked)]
extern "C" fn true_and_false() -> u64 {
naked_asm!(
"mov rax, 5",
#[cfg(true)]
#[cfg(false)]
"mov rax, 10",
"ret",
)
}
#[unsafe(naked)]
extern "C" fn false_and_true() -> u64 {
naked_asm!(
"mov rax, 5",
#[cfg(false)]
#[cfg(true)]
"mov rax, 10",
"ret",
)
}
pub fn main() {
std::cfg_select! {
reva => {
assert_eq!(const_operand(), 5);
assert_eq!(ignore_const_operand_cfg_attr(), 5);
assert_eq!(ignore_const_operand(), 5);
assert_eq!(first_template(), 5);
}
revb => {
assert_eq!(const_operand(), 10);
assert_eq!(ignore_const_operand_cfg_attr(), 10);
assert_eq!(ignore_const_operand(), 10);
assert_eq!(first_template(), 10);
}
}
options();
clobber_abi();
assert_eq!(true_and_false(), 5);
assert_eq!(false_and_true(), 5);
}

View file

@ -0,0 +1,48 @@
//@ only-x86_64
#![crate_type = "lib"]
use std::arch::{asm, global_asm, naked_asm};
global_asm!(
"nop",
#[cfg(false)]
//~^ ERROR the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable
"nop"
);
#[unsafe(naked)]
#[no_mangle]
extern "C" fn naked() {
naked_asm!(
"mov rax, 5",
#[cfg(false)]
//~^ ERROR the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable
"mov rax, {a}",
"ret",
#[cfg(false)]
//~^ ERROR the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable
a = const 10,
)
}
fn asm() {
unsafe {
asm!(
"nop",
#[cfg(false)]
//~^ ERROR the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable
clobber_abi("C"),
clobber_abi("C"), //~ ERROR `C` ABI specified multiple times
);
}
}
fn bad_attribute() {
unsafe {
asm!(
#[inline]
//~^ ERROR this attribute is not supported on assembly
"nop"
)
};
}

View file

@ -0,0 +1,57 @@
error[E0658]: the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable
--> $DIR/feature-gate-asm_cfg.rs:8:5
|
LL | #[cfg(false)]
| ^^^^^^^^^^^^^
|
= note: see issue #140364 <https://github.com/rust-lang/rust/issues/140364> for more information
= help: add `#![feature(asm_cfg)]` 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]: the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable
--> $DIR/feature-gate-asm_cfg.rs:18:9
|
LL | #[cfg(false)]
| ^^^^^^^^^^^^^
|
= note: see issue #140364 <https://github.com/rust-lang/rust/issues/140364> for more information
= help: add `#![feature(asm_cfg)]` 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]: the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable
--> $DIR/feature-gate-asm_cfg.rs:22:9
|
LL | #[cfg(false)]
| ^^^^^^^^^^^^^
|
= note: see issue #140364 <https://github.com/rust-lang/rust/issues/140364> for more information
= help: add `#![feature(asm_cfg)]` 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]: the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable
--> $DIR/feature-gate-asm_cfg.rs:32:13
|
LL | #[cfg(false)]
| ^^^^^^^^^^^^^
|
= note: see issue #140364 <https://github.com/rust-lang/rust/issues/140364> for more information
= help: add `#![feature(asm_cfg)]` 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: this attribute is not supported on assembly
--> $DIR/feature-gate-asm_cfg.rs:43:13
|
LL | #[inline]
| ^^^^^^^^^
error: `C` ABI specified multiple times
--> $DIR/feature-gate-asm_cfg.rs:35:13
|
LL | clobber_abi("C"),
| ---------------- previously specified here
LL | clobber_abi("C"),
| ^^^^^^^^^^^^^^^^
error: aborting due to 6 previous errors
For more information about this error, try `rustc --explain E0658`.