Skip to content

Commit 45d6aef

Browse files
authored
Rollup merge of rust-lang#73227 - camelid:multiple-asm-options, r=Amanieu
Allow multiple `asm!` options groups and report an error on duplicate options Fixes rust-lang#73193 Cc @joshtriplett @Amanieu - [x] Allow multiple options - [x] Update existing test - [x] Add new tests - [x] Check for duplicate options - [x] Add duplicate options tests - [x] Finalize suggestion format for duplicate options error
2 parents c47550f + c31785a commit 45d6aef

File tree

7 files changed

+236
-67
lines changed

7 files changed

+236
-67
lines changed

src/librustc_builtin_macros/asm.rs

+63-26
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ struct AsmArgs {
1616
named_args: FxHashMap<Symbol, usize>,
1717
reg_args: FxHashSet<usize>,
1818
options: ast::InlineAsmOptions,
19-
options_span: Option<Span>,
19+
options_spans: Vec<Span>,
2020
}
2121

2222
fn parse_args<'a>(
@@ -59,7 +59,7 @@ fn parse_args<'a>(
5959
named_args: FxHashMap::default(),
6060
reg_args: FxHashSet::default(),
6161
options: ast::InlineAsmOptions::empty(),
62-
options_span: None,
62+
options_spans: vec![],
6363
};
6464

6565
let mut allow_templates = true;
@@ -174,9 +174,9 @@ fn parse_args<'a>(
174174

175175
// Validate the order of named, positional & explicit register operands and options. We do
176176
// this at the end once we have the full span of the argument available.
177-
if let Some(options_span) = args.options_span {
177+
if !args.options_spans.is_empty() {
178178
ecx.struct_span_err(span, "arguments are not allowed after options")
179-
.span_label(options_span, "previous options")
179+
.span_labels(args.options_spans.clone(), "previous options")
180180
.span_label(span, "argument")
181181
.emit();
182182
}
@@ -227,23 +227,23 @@ fn parse_args<'a>(
227227
if args.options.contains(ast::InlineAsmOptions::NOMEM)
228228
&& args.options.contains(ast::InlineAsmOptions::READONLY)
229229
{
230-
let span = args.options_span.unwrap();
231-
ecx.struct_span_err(span, "the `nomem` and `readonly` options are mutually exclusive")
230+
let spans = args.options_spans.clone();
231+
ecx.struct_span_err(spans, "the `nomem` and `readonly` options are mutually exclusive")
232232
.emit();
233233
}
234234
if args.options.contains(ast::InlineAsmOptions::PURE)
235235
&& args.options.contains(ast::InlineAsmOptions::NORETURN)
236236
{
237-
let span = args.options_span.unwrap();
238-
ecx.struct_span_err(span, "the `pure` and `noreturn` options are mutually exclusive")
237+
let spans = args.options_spans.clone();
238+
ecx.struct_span_err(spans, "the `pure` and `noreturn` options are mutually exclusive")
239239
.emit();
240240
}
241241
if args.options.contains(ast::InlineAsmOptions::PURE)
242242
&& !args.options.intersects(ast::InlineAsmOptions::NOMEM | ast::InlineAsmOptions::READONLY)
243243
{
244-
let span = args.options_span.unwrap();
244+
let spans = args.options_spans.clone();
245245
ecx.struct_span_err(
246-
span,
246+
spans,
247247
"the `pure` option must be combined with either `nomem` or `readonly`",
248248
)
249249
.emit();
@@ -267,7 +267,7 @@ fn parse_args<'a>(
267267
}
268268
if args.options.contains(ast::InlineAsmOptions::PURE) && !have_real_output {
269269
ecx.struct_span_err(
270-
args.options_span.unwrap(),
270+
args.options_spans.clone(),
271271
"asm with `pure` option must have at least one output",
272272
)
273273
.emit();
@@ -283,27 +283,71 @@ fn parse_args<'a>(
283283
Ok(args)
284284
}
285285

286+
/// Report a duplicate option error.
287+
///
288+
/// This function must be called immediately after the option token is parsed.
289+
/// Otherwise, the suggestion will be incorrect.
290+
fn err_duplicate_option<'a>(p: &mut Parser<'a>, symbol: Symbol, span: Span) {
291+
let mut err = p
292+
.sess
293+
.span_diagnostic
294+
.struct_span_err(span, &format!("the `{}` option was already provided", symbol));
295+
err.span_label(span, "this option was already provided");
296+
297+
// Tool-only output
298+
let mut full_span = span;
299+
if p.token.kind == token::Comma {
300+
full_span = full_span.to(p.token.span);
301+
}
302+
err.tool_only_span_suggestion(
303+
full_span,
304+
"remove this option",
305+
String::new(),
306+
Applicability::MachineApplicable,
307+
);
308+
309+
err.emit();
310+
}
311+
312+
/// Try to set the provided option in the provided `AsmArgs`.
313+
/// If it is already set, report a duplicate option error.
314+
///
315+
/// This function must be called immediately after the option token is parsed.
316+
/// Otherwise, the error will not point to the correct spot.
317+
fn try_set_option<'a>(
318+
p: &mut Parser<'a>,
319+
args: &mut AsmArgs,
320+
symbol: Symbol,
321+
option: ast::InlineAsmOptions,
322+
) {
323+
if !args.options.contains(option) {
324+
args.options |= option;
325+
} else {
326+
err_duplicate_option(p, symbol, p.prev_token.span);
327+
}
328+
}
329+
286330
fn parse_options<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> Result<(), DiagnosticBuilder<'a>> {
287331
let span_start = p.prev_token.span;
288332

289333
p.expect(&token::OpenDelim(token::DelimToken::Paren))?;
290334

291335
while !p.eat(&token::CloseDelim(token::DelimToken::Paren)) {
292336
if p.eat(&token::Ident(sym::pure, false)) {
293-
args.options |= ast::InlineAsmOptions::PURE;
337+
try_set_option(p, args, sym::pure, ast::InlineAsmOptions::PURE);
294338
} else if p.eat(&token::Ident(sym::nomem, false)) {
295-
args.options |= ast::InlineAsmOptions::NOMEM;
339+
try_set_option(p, args, sym::nomem, ast::InlineAsmOptions::NOMEM);
296340
} else if p.eat(&token::Ident(sym::readonly, false)) {
297-
args.options |= ast::InlineAsmOptions::READONLY;
341+
try_set_option(p, args, sym::readonly, ast::InlineAsmOptions::READONLY);
298342
} else if p.eat(&token::Ident(sym::preserves_flags, false)) {
299-
args.options |= ast::InlineAsmOptions::PRESERVES_FLAGS;
343+
try_set_option(p, args, sym::preserves_flags, ast::InlineAsmOptions::PRESERVES_FLAGS);
300344
} else if p.eat(&token::Ident(sym::noreturn, false)) {
301-
args.options |= ast::InlineAsmOptions::NORETURN;
345+
try_set_option(p, args, sym::noreturn, ast::InlineAsmOptions::NORETURN);
302346
} else if p.eat(&token::Ident(sym::nostack, false)) {
303-
args.options |= ast::InlineAsmOptions::NOSTACK;
347+
try_set_option(p, args, sym::nostack, ast::InlineAsmOptions::NOSTACK);
304348
} else {
305349
p.expect(&token::Ident(sym::att_syntax, false))?;
306-
args.options |= ast::InlineAsmOptions::ATT_SYNTAX;
350+
try_set_option(p, args, sym::att_syntax, ast::InlineAsmOptions::ATT_SYNTAX);
307351
}
308352

309353
// Allow trailing commas
@@ -314,14 +358,7 @@ fn parse_options<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> Result<(), Diagn
314358
}
315359

316360
let new_span = span_start.to(p.prev_token.span);
317-
if let Some(options_span) = args.options_span {
318-
p.struct_span_err(new_span, "asm options cannot be specified multiple times")
319-
.span_label(options_span, "previously here")
320-
.span_label(new_span, "duplicate options")
321-
.emit();
322-
} else {
323-
args.options_span = Some(new_span);
324-
}
361+
args.options_spans.push(new_span);
325362

326363
Ok(())
327364
}
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// compile-flags: -O
2+
// only-x86_64
3+
4+
#![crate_type = "rlib"]
5+
#![feature(asm)]
6+
7+
// CHECK-LABEL: @pure
8+
// CHECK-NOT: asm
9+
// CHECK: ret void
10+
#[no_mangle]
11+
pub unsafe fn pure(x: i32) {
12+
let y: i32;
13+
asm!("", out("ax") y, in("bx") x, options(pure), options(nomem));
14+
}
15+
16+
pub static mut VAR: i32 = 0;
17+
pub static mut DUMMY_OUTPUT: i32 = 0;
18+
19+
// CHECK-LABEL: @readonly
20+
// CHECK: call i32 asm
21+
// CHECK: ret i32 1
22+
#[no_mangle]
23+
pub unsafe fn readonly() -> i32 {
24+
VAR = 1;
25+
asm!("", out("ax") DUMMY_OUTPUT, options(pure), options(readonly));
26+
VAR
27+
}
28+
29+
// CHECK-LABEL: @nomem
30+
// CHECK-NOT: store
31+
// CHECK: call i32 asm
32+
// CHECK: store
33+
// CHECK: ret i32 2
34+
#[no_mangle]
35+
pub unsafe fn nomem() -> i32 {
36+
VAR = 1;
37+
asm!("", out("ax") DUMMY_OUTPUT, options(pure), options(nomem));
38+
VAR = 2;
39+
VAR
40+
}
41+
42+
// CHECK-LABEL: @not_nomem
43+
// CHECK: store
44+
// CHECK: call i32 asm
45+
// CHECK: store
46+
// CHECK: ret i32 2
47+
#[no_mangle]
48+
pub unsafe fn not_nomem() -> i32 {
49+
VAR = 1;
50+
asm!("", out("ax") DUMMY_OUTPUT, options(pure), options(readonly));
51+
VAR = 2;
52+
VAR
53+
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// only-x86_64
2+
// run-rustfix
3+
4+
#![feature(asm)]
5+
6+
fn main() {
7+
unsafe {
8+
asm!("", options(nomem, ));
9+
//~^ ERROR the `nomem` option was already provided
10+
asm!("", options(att_syntax, ));
11+
//~^ ERROR the `att_syntax` option was already provided
12+
asm!("", options(nostack, att_syntax), options());
13+
//~^ ERROR the `nostack` option was already provided
14+
asm!("", options(nostack, ), options(), options());
15+
//~^ ERROR the `nostack` option was already provided
16+
//~| ERROR the `nostack` option was already provided
17+
//~| ERROR the `nostack` option was already provided
18+
asm!(
19+
"",
20+
options(nomem, noreturn),
21+
options(att_syntax, ), //~ ERROR the `noreturn` option was already provided
22+
options( nostack), //~ ERROR the `nomem` option was already provided
23+
options(), //~ ERROR the `noreturn` option was already provided
24+
);
25+
}
26+
}

src/test/ui/asm/duplicate-options.rs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// only-x86_64
2+
// run-rustfix
3+
4+
#![feature(asm)]
5+
6+
fn main() {
7+
unsafe {
8+
asm!("", options(nomem, nomem));
9+
//~^ ERROR the `nomem` option was already provided
10+
asm!("", options(att_syntax, att_syntax));
11+
//~^ ERROR the `att_syntax` option was already provided
12+
asm!("", options(nostack, att_syntax), options(nostack));
13+
//~^ ERROR the `nostack` option was already provided
14+
asm!("", options(nostack, nostack), options(nostack), options(nostack));
15+
//~^ ERROR the `nostack` option was already provided
16+
//~| ERROR the `nostack` option was already provided
17+
//~| ERROR the `nostack` option was already provided
18+
asm!(
19+
"",
20+
options(nomem, noreturn),
21+
options(att_syntax, noreturn), //~ ERROR the `noreturn` option was already provided
22+
options(nomem, nostack), //~ ERROR the `nomem` option was already provided
23+
options(noreturn), //~ ERROR the `noreturn` option was already provided
24+
);
25+
}
26+
}
+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
error: the `nomem` option was already provided
2+
--> $DIR/duplicate-options.rs:8:33
3+
|
4+
LL | asm!("", options(nomem, nomem));
5+
| ^^^^^ this option was already provided
6+
7+
error: the `att_syntax` option was already provided
8+
--> $DIR/duplicate-options.rs:10:38
9+
|
10+
LL | asm!("", options(att_syntax, att_syntax));
11+
| ^^^^^^^^^^ this option was already provided
12+
13+
error: the `nostack` option was already provided
14+
--> $DIR/duplicate-options.rs:12:56
15+
|
16+
LL | asm!("", options(nostack, att_syntax), options(nostack));
17+
| ^^^^^^^ this option was already provided
18+
19+
error: the `nostack` option was already provided
20+
--> $DIR/duplicate-options.rs:14:35
21+
|
22+
LL | asm!("", options(nostack, nostack), options(nostack), options(nostack));
23+
| ^^^^^^^ this option was already provided
24+
25+
error: the `nostack` option was already provided
26+
--> $DIR/duplicate-options.rs:14:53
27+
|
28+
LL | asm!("", options(nostack, nostack), options(nostack), options(nostack));
29+
| ^^^^^^^ this option was already provided
30+
31+
error: the `nostack` option was already provided
32+
--> $DIR/duplicate-options.rs:14:71
33+
|
34+
LL | asm!("", options(nostack, nostack), options(nostack), options(nostack));
35+
| ^^^^^^^ this option was already provided
36+
37+
error: the `noreturn` option was already provided
38+
--> $DIR/duplicate-options.rs:21:33
39+
|
40+
LL | options(att_syntax, noreturn),
41+
| ^^^^^^^^ this option was already provided
42+
43+
error: the `nomem` option was already provided
44+
--> $DIR/duplicate-options.rs:22:21
45+
|
46+
LL | options(nomem, nostack),
47+
| ^^^^^ this option was already provided
48+
49+
error: the `noreturn` option was already provided
50+
--> $DIR/duplicate-options.rs:23:21
51+
|
52+
LL | options(noreturn),
53+
| ^^^^^^^^ this option was already provided
54+
55+
error: aborting due to 9 previous errors
56+

src/test/ui/asm/parse-error.rs

-5
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,6 @@ fn main() {
3434
//~^ ERROR expected one of
3535
asm!("", options(nomem, foo));
3636
//~^ ERROR expected one of
37-
asm!("", options(), options());
38-
//~^ ERROR asm options cannot be specified multiple times
39-
asm!("", options(), options(), options());
40-
//~^ ERROR asm options cannot be specified multiple times
41-
//~^^ ERROR asm options cannot be specified multiple times
4237
asm!("{}", options(), const foo);
4338
//~^ ERROR arguments are not allowed after options
4439
asm!("{a}", a = const foo, a = const bar);

0 commit comments

Comments
 (0)