Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for specifying multiple clobber_abi in asm! #89316

Merged
merged 4 commits into from
Nov 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2058,7 +2058,7 @@ pub struct InlineAsm {
pub template: Vec<InlineAsmTemplatePiece>,
pub template_strs: Box<[(Symbol, Option<Symbol>, Span)]>,
pub operands: Vec<(InlineAsmOperand, Span)>,
pub clobber_abi: Option<(Symbol, Span)>,
pub clobber_abis: Vec<(Symbol, Span)>,
pub options: InlineAsmOptions,
pub line_spans: Vec<Span>,
}
Expand Down Expand Up @@ -2715,7 +2715,7 @@ pub enum ItemKind {
/// E.g., `extern {}` or `extern "C" {}`.
ForeignMod(ForeignMod),
/// Module-level inline assembly (from `global_asm!()`).
GlobalAsm(InlineAsm),
GlobalAsm(Box<InlineAsm>),
/// A type alias (`type`).
///
/// E.g., `type Foo = Bar<u8>;`.
Expand Down
47 changes: 40 additions & 7 deletions compiler/rustc_ast_lowering/src/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::LoweringContext;

use rustc_ast::*;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::stable_set::FxHashSet;
use rustc_errors::struct_span_err;
use rustc_hir as hir;
use rustc_session::parse::feature_err;
Expand Down Expand Up @@ -49,22 +50,47 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
.emit();
}

let mut clobber_abi = None;
let mut clobber_abis = FxHashMap::default();
Copy link
Contributor

@nbdd0121 nbdd0121 Oct 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this shall be a Vec. If there are multiple occurrence of the same ABI then it should be error (and emitted during macro expansion), not silently merged.

if let Some(asm_arch) = asm_arch {
if let Some((abi_name, abi_span)) = asm.clobber_abi {
match asm::InlineAsmClobberAbi::parse(asm_arch, &self.sess.target, abi_name) {
Ok(abi) => clobber_abi = Some((abi, abi_span)),
for (abi_name, abi_span) in &asm.clobber_abis {
match asm::InlineAsmClobberAbi::parse(asm_arch, &self.sess.target, *abi_name) {
Ok(abi) => {
// If the abi was already in the list, emit an error
match clobber_abis.get(&abi) {
Some((prev_name, prev_sp)) => {
let mut err = self.sess.struct_span_err(
*abi_span,
&format!("`{}` ABI specified multiple times", prev_name),
);
err.span_label(*prev_sp, "previously specified here");

// Multiple different abi names may actually be the same ABI
// If the specified ABIs are not the same name, alert the user that they resolve to the same ABI
let source_map = self.sess.source_map();
if source_map.span_to_snippet(*prev_sp)
!= source_map.span_to_snippet(*abi_span)
{
err.note("these ABIs are equivalent on the current target");
}

err.emit();
}
None => {
clobber_abis.insert(abi, (abi_name, *abi_span));
}
}
}
Err(&[]) => {
self.sess
.struct_span_err(
abi_span,
*abi_span,
"`clobber_abi` is not supported on this target",
)
.emit();
}
Err(supported_abis) => {
let mut err =
self.sess.struct_span_err(abi_span, "invalid ABI for `clobber_abi`");
self.sess.struct_span_err(*abi_span, "invalid ABI for `clobber_abi`");
let mut abis = format!("`{}`", supported_abis[0]);
for m in &supported_abis[1..] {
let _ = write!(abis, ", `{}`", m);
Expand Down Expand Up @@ -348,8 +374,14 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {

// If a clobber_abi is specified, add the necessary clobbers to the
// operands list.
if let Some((abi, abi_span)) = clobber_abi {
let mut clobbered = FxHashSet::default();
for (abi, (_, abi_span)) in clobber_abis {
for &clobber in abi.clobbered_regs() {
// Don't emit a clobber for a register already clobbered
if clobbered.contains(&clobber) {
continue;
}

let mut output_used = false;
clobber.overlapping_regs(|reg| {
if used_output_regs.contains_key(&reg) {
Expand All @@ -366,6 +398,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
},
self.lower_span(abi_span),
));
clobbered.insert(clobber);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_ast_pretty/src/pprust/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2235,8 +2235,8 @@ impl<'a> State<'a> {

let mut args = vec![AsmArg::Template(InlineAsmTemplatePiece::to_string(&asm.template))];
args.extend(asm.operands.iter().map(|(o, _)| AsmArg::Operand(o)));
if let Some((abi, _)) = asm.clobber_abi {
args.push(AsmArg::ClobberAbi(abi));
for (abi, _) in &asm.clobber_abis {
args.push(AsmArg::ClobberAbi(*abi));
}
if !asm.options.is_empty() {
args.push(AsmArg::Options(asm.options));
Expand Down
92 changes: 61 additions & 31 deletions compiler/rustc_builtin_macros/src/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct AsmArgs {
operands: Vec<(ast::InlineAsmOperand, Span)>,
named_args: FxHashMap<Symbol, usize>,
reg_args: FxHashSet<usize>,
clobber_abi: Option<(Symbol, Span)>,
clobber_abis: Vec<(Symbol, Span)>,
options: ast::InlineAsmOptions,
options_spans: Vec<Span>,
}
Expand Down Expand Up @@ -64,7 +64,7 @@ fn parse_args<'a>(
operands: vec![],
named_args: FxHashMap::default(),
reg_args: FxHashSet::default(),
clobber_abi: None,
clobber_abis: Vec::new(),
options: ast::InlineAsmOptions::empty(),
options_spans: vec![],
};
Expand Down Expand Up @@ -210,9 +210,9 @@ fn parse_args<'a>(
.span_labels(args.options_spans.clone(), "previous options")
.span_label(span, "argument")
.emit();
} else if let Some((_, abi_span)) = args.clobber_abi {
} else if let Some((_, abi_span)) = args.clobber_abis.last() {
ecx.struct_span_err(span, "arguments are not allowed after clobber_abi")
.span_label(abi_span, "clobber_abi")
.span_label(*abi_span, "clobber_abi")
.span_label(span, "argument")
.emit();
}
Expand Down Expand Up @@ -322,10 +322,13 @@ fn parse_args<'a>(
// Bail out now since this is likely to confuse MIR
return Err(err);
}
if let Some((_, abi_span)) = args.clobber_abi {

if args.clobber_abis.len() > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if args.clobber_abis.len() > 0 {
if !args.clobber_abis.is_empty() {

if is_global_asm {
let err =
ecx.struct_span_err(abi_span, "`clobber_abi` cannot be used with `global_asm!`");
let err = ecx.struct_span_err(
args.clobber_abis.iter().map(|(_, span)| *span).collect::<Vec<Span>>(),
"`clobber_abi` cannot be used with `global_asm!`",
);

// Bail out now since this is likely to confuse later stages
return Err(err);
Expand All @@ -335,7 +338,10 @@ fn parse_args<'a>(
regclass_outputs.clone(),
"asm with `clobber_abi` must specify explicit registers for outputs",
)
.span_label(abi_span, "clobber_abi")
.span_labels(
args.clobber_abis.iter().map(|(_, span)| *span).collect::<Vec<Span>>(),
"clobber_abi",
)
.span_labels(regclass_outputs, "generic outputs")
.emit();
}
Expand Down Expand Up @@ -439,37 +445,61 @@ fn parse_clobber_abi<'a>(

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

let clobber_abi = match p.parse_str_lit() {
Ok(str_lit) => str_lit.symbol_unescaped,
Err(opt_lit) => {
let span = opt_lit.map_or(p.token.span, |lit| lit.span);
let mut err = p.sess.span_diagnostic.struct_span_err(span, "expected string literal");
err.span_label(span, "not a string literal");
return Err(err);
}
};
if p.eat(&token::CloseDelim(token::DelimToken::Paren)) {
let err = p.sess.span_diagnostic.struct_span_err(
p.token.span,
"at least one abi must be provided as an argument to `clobber_abi`",
);
return Err(err);
}

p.expect(&token::CloseDelim(token::DelimToken::Paren))?;
let mut new_abis = Vec::new();
loop {
match p.parse_str_lit() {
Ok(str_lit) => {
new_abis.push((str_lit.symbol_unescaped, str_lit.span));
}
Err(opt_lit) => {
// If the non-string literal is a closing paren then it's the end of the list and is fine
if p.eat(&token::CloseDelim(token::DelimToken::Paren)) {
break;
}
let span = opt_lit.map_or(p.token.span, |lit| lit.span);
let mut err =
p.sess.span_diagnostic.struct_span_err(span, "expected string literal");
err.span_label(span, "not a string literal");
return Err(err);
}
};

let new_span = span_start.to(p.prev_token.span);
// Allow trailing commas
if p.eat(&token::CloseDelim(token::DelimToken::Paren)) {
break;
}
p.expect(&token::Comma)?;
}

if let Some((_, prev_span)) = args.clobber_abi {
let mut err = p
.sess
.span_diagnostic
.struct_span_err(new_span, "clobber_abi specified multiple times");
err.span_label(prev_span, "clobber_abi previously specified here");
return Err(err);
} else if !args.options_spans.is_empty() {
let full_span = span_start.to(p.prev_token.span);

if !args.options_spans.is_empty() {
let mut err = p
.sess
.span_diagnostic
.struct_span_err(new_span, "clobber_abi is not allowed after options");
.struct_span_err(full_span, "clobber_abi is not allowed after options");
err.span_labels(args.options_spans.clone(), "options");
return Err(err);
}

args.clobber_abi = Some((clobber_abi, new_span));
match &new_abis[..] {
// should have errored above during parsing
[] => unreachable!(),
[(abi, _span)] => args.clobber_abis.push((*abi, full_span)),
[abis @ ..] => {
for (abi, span) in abis {
args.clobber_abis.push((*abi, *span));
}
}
}

Ok(())
}
Expand Down Expand Up @@ -770,7 +800,7 @@ fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, args: AsmArgs) -> Option<ast::Inl
template,
template_strs: template_strs.into_boxed_slice(),
operands: args.operands,
clobber_abi: args.clobber_abi,
clobber_abis: args.clobber_abis,
options: args.options,
line_spans,
})
Expand Down Expand Up @@ -815,7 +845,7 @@ pub fn expand_global_asm<'cx>(
ident: Ident::empty(),
attrs: Vec::new(),
id: ast::DUMMY_NODE_ID,
kind: ast::ItemKind::GlobalAsm(inline_asm),
kind: ast::ItemKind::GlobalAsm(Box::new(inline_asm)),
vis: ast::Visibility {
span: sp.shrink_to_lo(),
kind: ast::VisibilityKind::Inherited,
Expand Down
8 changes: 5 additions & 3 deletions src/doc/unstable-book/src/library-features/asm.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ fn call_foo(arg: i32) -> i32 {

Note that the `fn` or `static` item does not need to be public or `#[no_mangle]`: the compiler will automatically insert the appropriate mangled symbol name into the assembly code.

By default, `asm!` assumes that any register not specified as an output will have its contents preserved by the assembly code. The [`clobber_abi`](#abi-clobbers) argument to `asm!` tells the compiler to automatically insert the necessary clobber operands according to the given calling convention ABI: any register which is not fully preserved in that ABI will be treated as clobbered.
By default, `asm!` assumes that any register not specified as an output will have its contents preserved by the assembly code. The [`clobber_abi`](#abi-clobbers) argument to `asm!` tells the compiler to automatically insert the necessary clobber operands according to the given calling convention ABI: any register which is not fully preserved in that ABI will be treated as clobbered. Multiple `clobber_abi` arguments may be provided and all clobbers from all specified ABIs will be inserted.

## Register template modifiers

Expand Down Expand Up @@ -453,10 +453,10 @@ reg_spec := <register class> / "<explicit register>"
operand_expr := expr / "_" / expr "=>" expr / expr "=>" "_"
reg_operand := dir_spec "(" reg_spec ")" operand_expr
operand := reg_operand / "const" const_expr / "sym" path
clobber_abi := "clobber_abi(" <abi> ")"
clobber_abi := "clobber_abi(" <abi> *["," <abi>] [","] ")"
option := "pure" / "nomem" / "readonly" / "preserves_flags" / "noreturn" / "nostack" / "att_syntax" / "raw"
options := "options(" option *["," option] [","] ")"
asm := "asm!(" format_string *("," format_string) *("," [ident "="] operand) ["," clobber_abi] *("," options) [","] ")"
asm := "asm!(" format_string *("," format_string) *("," [ident "="] operand) *("," clobber_abi) *("," options) [","] ")"
```

Inline assembly is currently supported on the following architectures:
Expand Down Expand Up @@ -799,6 +799,8 @@ As stated in the previous section, passing an input value smaller than the regis

The `clobber_abi` keyword can be used to apply a default set of clobbers to an `asm` block. This will automatically insert the necessary clobber constraints as needed for calling a function with a particular calling convention: if the calling convention does not fully preserve the value of a register across a call then a `lateout("reg") _` is implicitly added to the operands list.

`clobber_abi` may be specified any number of times. It will insert a clobber for all unique registers in the union of all specified calling conventions.

Generic register class outputs are disallowed by the compiler when `clobber_abi` is used: all outputs must specify an explicit register. Explicit register outputs have precedence over the implicit clobbers inserted by `clobber_abi`: a clobber will only be inserted for a register if that register is not used as an output.
The following ABIs can be used with `clobber_abi`:

Expand Down
12 changes: 4 additions & 8 deletions src/test/ui/asm/aarch64/parse-error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,16 @@ fn main() {
asm!("", clobber_abi(foo));
//~^ ERROR expected string literal
asm!("", clobber_abi("C" foo));
//~^ ERROR expected `)`, found `foo`
//~^ ERROR expected one of `)` or `,`, found `foo`
asm!("", clobber_abi("C", foo));
//~^ ERROR expected `)`, found `,`
//~^ ERROR expected string literal
asm!("{}", clobber_abi("C"), const foo);
//~^ ERROR arguments are not allowed after clobber_abi
//~^^ ERROR attempt to use a non-constant value in a constant
asm!("", options(), clobber_abi("C"));
//~^ ERROR clobber_abi is not allowed after options
asm!("{}", options(), clobber_abi("C"), const foo);
//~^ ERROR clobber_abi is not allowed after options
asm!("", clobber_abi("C"), clobber_abi("C"));
//~^ ERROR clobber_abi specified multiple times
asm!("{a}", a = const foo, a = const bar);
//~^ ERROR duplicate argument named `a`
//~^^ ERROR argument never used
Expand Down Expand Up @@ -110,18 +108,16 @@ global_asm!("{}", options(), const FOO);
global_asm!("", clobber_abi(FOO));
//~^ ERROR expected string literal
global_asm!("", clobber_abi("C" FOO));
//~^ ERROR expected `)`, found `FOO`
//~^ ERROR expected one of `)` or `,`, found `FOO`
global_asm!("", clobber_abi("C", FOO));
//~^ ERROR expected `)`, found `,`
//~^ ERROR expected string literal
global_asm!("{}", clobber_abi("C"), const FOO);
//~^ ERROR arguments are not allowed after clobber_abi
//~^^ ERROR `clobber_abi` cannot be used with `global_asm!`
global_asm!("", options(), clobber_abi("C"));
//~^ ERROR clobber_abi is not allowed after options
global_asm!("{}", options(), clobber_abi("C"), const FOO);
//~^ ERROR clobber_abi is not allowed after options
global_asm!("", clobber_abi("C"), clobber_abi("C"));
//~^ ERROR clobber_abi specified multiple times
global_asm!("{a}", a = const FOO, a = const BAR);
//~^ ERROR duplicate argument named `a`
//~^^ ERROR argument never used
Expand Down
Loading