Skip to content

Commit 6cc60a6

Browse files
committed
support #[cfg(...)] on arguments to the asm! macros
1 parent d30124a commit 6cc60a6

File tree

10 files changed

+373
-3
lines changed

10 files changed

+373
-3
lines changed

compiler/rustc_builtin_macros/messages.ftl

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
builtin_macros_alloc_error_must_be_fn = alloc_error_handler must be a function
22
builtin_macros_alloc_must_statics = allocators must be statics
33
4+
builtin_macros_asm_attribute_not_supported =
5+
this attribute is not supported on assembly
6+
builtin_macros_asm_cfg =
7+
the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable
8+
49
builtin_macros_asm_clobber_abi = clobber_abi
510
builtin_macros_asm_clobber_no_reg = asm with `clobber_abi` must specify explicit registers for outputs
611
builtin_macros_asm_clobber_outputs = generic outputs

compiler/rustc_builtin_macros/src/asm.rs

+84-3
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,20 @@ use rustc_index::bit_set::GrowableBitSet;
1010
use rustc_parse::exp;
1111
use rustc_parse::parser::{ExpKeywordPair, Parser};
1212
use rustc_session::lint;
13-
use rustc_span::{ErrorGuaranteed, InnerSpan, Span, Symbol, kw};
13+
use rustc_session::parse::feature_err;
14+
use rustc_span::{ErrorGuaranteed, InnerSpan, Span, Symbol, kw, sym};
1415
use rustc_target::asm::InlineAsmArch;
1516
use smallvec::smallvec;
1617
use {rustc_ast as ast, rustc_parse_format as parse};
1718

18-
use crate::errors;
1919
use crate::util::{ExprToSpannedString, expr_to_spanned_string};
20+
use crate::{errors, fluent_generated as fluent};
2021

2122
/// An argument to one of the `asm!` macros. The argument is syntactically valid, but is otherwise
2223
/// not validated at all.
2324
pub struct RawAsmArg {
2425
pub kind: RawAsmArgKind,
26+
pub attributes: AsmAttrVec,
2527
pub span: Span,
2628
}
2729

@@ -43,6 +45,48 @@ struct AsmArgs {
4345
pub options_spans: Vec<Span>,
4446
}
4547

48+
/// A parsed list of attributes that is not attached to any item.
49+
/// Used to check whether `asm!` arguments are configured out.
50+
pub struct AsmAttrVec(pub ast::AttrVec);
51+
52+
impl AsmAttrVec {
53+
fn parse<'a>(p: &mut Parser<'a>) -> PResult<'a, Self> {
54+
let mut attributes = ast::AttrVec::new();
55+
loop {
56+
if p.token != token::Pound {
57+
break;
58+
}
59+
60+
let attr = p.parse_attribute(rustc_parse::parser::attr::InnerAttrPolicy::Permitted)?;
61+
attributes.push(attr);
62+
}
63+
64+
Ok(Self(attributes))
65+
}
66+
}
67+
impl ast::HasAttrs for AsmAttrVec {
68+
// Follows `ast::Expr`.
69+
const SUPPORTS_CUSTOM_INNER_ATTRS: bool = false;
70+
71+
fn attrs(&self) -> &[rustc_ast::Attribute] {
72+
&self.0
73+
}
74+
75+
fn visit_attrs(&mut self, f: impl FnOnce(&mut rustc_ast::AttrVec)) {
76+
f(&mut self.0)
77+
}
78+
}
79+
80+
impl ast::HasTokens for AsmAttrVec {
81+
fn tokens(&self) -> Option<&rustc_ast::tokenstream::LazyAttrTokenStream> {
82+
None
83+
}
84+
85+
fn tokens_mut(&mut self) -> Option<&mut Option<rustc_ast::tokenstream::LazyAttrTokenStream>> {
86+
None
87+
}
88+
}
89+
4690
/// Used for better error messages when operand types are used that are not
4791
/// supported by the current macro (e.g. `in` or `out` for `global_asm!`)
4892
///
@@ -158,10 +202,12 @@ pub fn parse_raw_asm_args<'a>(
158202

159203
let mut args = Vec::new();
160204

205+
let attributes = AsmAttrVec::parse(p)?;
161206
let first_template = p.parse_expr()?;
162207
args.push(RawAsmArg {
163208
span: first_template.span,
164209
kind: RawAsmArgKind::Template(first_template),
210+
attributes,
165211
});
166212

167213
let mut allow_templates = true;
@@ -180,6 +226,7 @@ pub fn parse_raw_asm_args<'a>(
180226
break;
181227
} // accept trailing commas
182228

229+
let attributes = AsmAttrVec::parse(p)?;
183230
let span_start = p.token.span;
184231

185232
// Parse clobber_abi
@@ -189,6 +236,7 @@ pub fn parse_raw_asm_args<'a>(
189236
args.push(RawAsmArg {
190237
kind: RawAsmArgKind::ClobberAbi(parse_clobber_abi(p)?),
191238
span: span_start.to(p.prev_token.span),
239+
attributes,
192240
});
193241

194242
continue;
@@ -201,6 +249,7 @@ pub fn parse_raw_asm_args<'a>(
201249
args.push(RawAsmArg {
202250
kind: RawAsmArgKind::Options(parse_options(p, asm_macro)?),
203251
span: span_start.to(p.prev_token.span),
252+
attributes,
204253
});
205254

206255
continue;
@@ -223,6 +272,7 @@ pub fn parse_raw_asm_args<'a>(
223272
args.push(RawAsmArg {
224273
span: span_start.to(p.prev_token.span),
225274
kind: RawAsmArgKind::Operand(name, op),
275+
attributes,
226276
});
227277
} else if allow_templates {
228278
let template = p.parse_expr()?;
@@ -244,7 +294,11 @@ pub fn parse_raw_asm_args<'a>(
244294
}
245295
}
246296

247-
args.push(RawAsmArg { span: template.span, kind: RawAsmArgKind::Template(template) });
297+
args.push(RawAsmArg {
298+
span: template.span,
299+
kind: RawAsmArgKind::Template(template),
300+
attributes,
301+
});
248302
} else {
249303
p.unexpected_any()?
250304
}
@@ -270,6 +324,13 @@ fn validate_raw_asm_args<'a>(
270324
) -> PResult<'a, AsmArgs> {
271325
let dcx = ecx.dcx();
272326

327+
let strip_unconfigured = rustc_expand::config::StripUnconfigured {
328+
sess: ecx.sess,
329+
features: Some(ecx.ecfg.features),
330+
config_tokens: false,
331+
lint_node_id: ecx.current_expansion.lint_node_id,
332+
};
333+
273334
let mut args = AsmArgs {
274335
templates: vec![],
275336
operands: vec![],
@@ -283,6 +344,26 @@ fn validate_raw_asm_args<'a>(
283344
let mut allow_templates = true;
284345

285346
for arg in raw_args {
347+
for attr in arg.attributes.0.iter() {
348+
match attr.name() {
349+
Some(sym::cfg | sym::cfg_attr) => {
350+
if !ecx.ecfg.features.asm_cfg() {
351+
let span = attr.span();
352+
feature_err(ecx.sess, sym::asm_cfg, span, fluent::builtin_macros_asm_cfg)
353+
.emit();
354+
}
355+
}
356+
_ => {
357+
ecx.dcx().emit_err(errors::AsmAttributeNotSupported { span: attr.span() });
358+
}
359+
}
360+
}
361+
362+
// Skip arguments that are configured out.
363+
if ecx.ecfg.features.asm_cfg() && strip_unconfigured.configure(arg.attributes).is_none() {
364+
continue;
365+
}
366+
286367
match arg.kind {
287368
RawAsmArgKind::Template(template) => {
288369
// The error for the first template is delayed.

compiler/rustc_builtin_macros/src/errors.rs

+7
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,13 @@ pub(crate) struct AsmRequiresTemplate {
795795
pub(crate) span: Span,
796796
}
797797

798+
#[derive(Diagnostic)]
799+
#[diag(builtin_macros_asm_attribute_not_supported)]
800+
pub(crate) struct AsmAttributeNotSupported {
801+
#[primary_span]
802+
pub(crate) span: Span,
803+
}
804+
798805
#[derive(Diagnostic)]
799806
#[diag(builtin_macros_asm_expected_comma)]
800807
pub(crate) struct AsmExpectedComma {

compiler/rustc_feature/src/unstable.rs

+2
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,8 @@ declare_features! (
369369
(unstable, arbitrary_self_types, "1.23.0", Some(44874)),
370370
/// Allows inherent and trait methods with arbitrary self types that are raw pointers.
371371
(unstable, arbitrary_self_types_pointers, "1.83.0", Some(44874)),
372+
/// Allows #[cfg(...)] on inline assembly templates and operands.
373+
(unstable, asm_cfg, "CURRENT_RUSTC_VERSION", Some(140364)),
372374
/// Enables experimental inline assembly support for additional architectures.
373375
(unstable, asm_experimental_arch, "1.58.0", Some(93335)),
374376
/// Enables experimental register support in inline assembly.

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@ symbols! {
472472
as_ref,
473473
as_str,
474474
asm,
475+
asm_cfg,
475476
asm_const,
476477
asm_experimental_arch,
477478
asm_experimental_reg,

tests/ui/asm/cfg-parse-error.rs

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//@ needs-asm-support
2+
#![feature(asm_cfg)]
3+
4+
use std::arch::asm;
5+
6+
fn main() {
7+
unsafe {
8+
// Templates are not allowed after operands (even if the operands are configured out).
9+
asm!(
10+
"",
11+
#[cfg(false)]
12+
clobber_abi("C"),
13+
#[cfg(false)]
14+
options(att_syntax),
15+
#[cfg(false)]
16+
a = out(reg) x,
17+
"",
18+
//~^ ERROR expected one of `clobber_abi`, `const`
19+
);
20+
asm!(
21+
#[cfg(false)]
22+
"",
23+
#[cfg(false)]
24+
const {
25+
5
26+
},
27+
"", //~ ERROR expected one of `clobber_abi`, `const`
28+
);
29+
30+
// This is currently accepted because `const { 5 }` parses as an expression.
31+
asm!(
32+
#[cfg(false)]
33+
const {
34+
5
35+
},
36+
"",
37+
);
38+
// This is not accepted because `a = out(reg) x` is not a valid expresion.
39+
asm!(
40+
#[cfg(false)]
41+
a = out(reg) x, //~ ERROR expected token: `,`
42+
"",
43+
);
44+
}
45+
}

tests/ui/asm/cfg-parse-error.stderr

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
error: expected one of `clobber_abi`, `const`, `in`, `inlateout`, `inout`, `label`, `lateout`, `options`, `out`, or `sym`, found `""`
2+
--> $DIR/cfg-parse-error.rs:17:13
3+
|
4+
LL | a = out(reg) x,
5+
| - expected one of 10 possible tokens
6+
LL | "",
7+
| ^^ unexpected token
8+
9+
error: expected one of `clobber_abi`, `const`, `in`, `inlateout`, `inout`, `label`, `lateout`, `options`, `out`, or `sym`, found `""`
10+
--> $DIR/cfg-parse-error.rs:27:13
11+
|
12+
LL | },
13+
| - expected one of 10 possible tokens
14+
LL | "",
15+
| ^^ unexpected token
16+
17+
error: expected token: `,`
18+
--> $DIR/cfg-parse-error.rs:41:26
19+
|
20+
LL | a = out(reg) x,
21+
| ^ expected `,`
22+
23+
error: aborting due to 3 previous errors
24+

tests/ui/asm/cfg.rs

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Check that `cfg` and `cfg_attr` work as expected.
2+
//
3+
//@ revisions: reva revb
4+
//@ only-x86_64
5+
//@ run-pass
6+
#![feature(asm_cfg, cfg_match)]
7+
8+
use std::arch::{asm, naked_asm};
9+
10+
#[unsafe(naked)]
11+
extern "C" fn ignore_const_operand() -> u64 {
12+
naked_asm!(
13+
"mov rax, 5",
14+
#[cfg(revb)]
15+
"mov rax, {a}",
16+
"ret",
17+
#[cfg(revb)]
18+
a = const 10,
19+
)
20+
}
21+
22+
#[unsafe(naked)]
23+
extern "C" fn ignore_const_operand_cfg_attr() -> u64 {
24+
naked_asm!(
25+
"mov rax, 5",
26+
#[cfg_attr(true, cfg(revb))]
27+
"mov rax, {a}",
28+
"ret",
29+
#[cfg_attr(true, cfg(revb))]
30+
a = const 10,
31+
)
32+
}
33+
34+
#[unsafe(naked)]
35+
extern "C" fn const_operand() -> u64 {
36+
naked_asm!(
37+
"mov rax, {a}",
38+
"ret",
39+
#[cfg(reva)]
40+
a = const 5,
41+
#[cfg(revb)]
42+
a = const 10,
43+
)
44+
}
45+
46+
fn options() {
47+
// Without the cfg, this throws an error that the `noreturn` option is provided twice.
48+
unsafe {
49+
asm!(
50+
"nop",
51+
#[cfg(false)]
52+
options(att_syntax),
53+
options(att_syntax)
54+
)
55+
}
56+
}
57+
58+
fn clobber_abi() {
59+
// Without the cfg, this throws an error that the "C" abi is provided twice.
60+
unsafe {
61+
asm!(
62+
"nop",
63+
#[cfg(false)]
64+
clobber_abi("C"),
65+
clobber_abi("C"),
66+
);
67+
}
68+
}
69+
70+
#[unsafe(naked)]
71+
extern "C" fn first_template() -> u64 {
72+
naked_asm!(
73+
#[cfg(reva)]
74+
"mov rax, 5",
75+
#[cfg(revb)]
76+
"mov rax, 10",
77+
"ret",
78+
)
79+
}
80+
81+
pub fn main() {
82+
std::cfg_match! {
83+
reva => {
84+
assert_eq!(const_operand(), 5);
85+
assert_eq!(ignore_const_operand_cfg_attr(), 5);
86+
assert_eq!(ignore_const_operand(), 5);
87+
assert_eq!(first_template(), 5);
88+
89+
}
90+
revb => {
91+
assert_eq!(const_operand(), 10);
92+
assert_eq!(ignore_const_operand_cfg_attr(), 10);
93+
assert_eq!(ignore_const_operand(), 10);
94+
assert_eq!(first_template(), 10);
95+
96+
}
97+
}
98+
options();
99+
clobber_abi();
100+
}

0 commit comments

Comments
 (0)