386 lines
13 KiB
Rust
386 lines
13 KiB
Rust
|
|
use rustc_ast::ptr::P;
|
||
|
|
use rustc_ast::{self as ast, AsmMacro};
|
||
|
|
use rustc_span::{Span, Symbol, kw};
|
||
|
|
|
||
|
|
use super::{ExpKeywordPair, ForceCollect, IdentIsRaw, Trailing, UsePreAttrPos};
|
||
|
|
use crate::{PResult, Parser, errors, exp, token};
|
||
|
|
|
||
|
|
/// 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,
|
||
|
|
}
|
||
|
|
|
||
|
|
pub enum AsmArgKind {
|
||
|
|
Template(P<ast::Expr>),
|
||
|
|
Operand(Option<Symbol>, ast::InlineAsmOperand),
|
||
|
|
Options(Vec<AsmOption>),
|
||
|
|
ClobberAbi(Vec<(Symbol, Span)>),
|
||
|
|
}
|
||
|
|
|
||
|
|
pub struct AsmOption {
|
||
|
|
pub symbol: Symbol,
|
||
|
|
pub span: Span,
|
||
|
|
// A bitset, with only the bit for this option's symbol set.
|
||
|
|
pub options: ast::InlineAsmOptions,
|
||
|
|
// Used when suggesting to remove an option.
|
||
|
|
pub span_with_comma: 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 attrs = p.parse_outer_attributes()?;
|
||
|
|
|
||
|
|
p.collect_tokens(None, attrs, ForceCollect::No, |_, attrs| {
|
||
|
|
Ok((Self(attrs), Trailing::No, UsePreAttrPos::No))
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
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!`)
|
||
|
|
///
|
||
|
|
/// returns
|
||
|
|
///
|
||
|
|
/// - `Ok(true)` if the current token matches the keyword, and was expected
|
||
|
|
/// - `Ok(false)` if the current token does not match the keyword
|
||
|
|
/// - `Err(_)` if the current token matches the keyword, but was not expected
|
||
|
|
fn eat_operand_keyword<'a>(
|
||
|
|
p: &mut Parser<'a>,
|
||
|
|
exp: ExpKeywordPair,
|
||
|
|
asm_macro: AsmMacro,
|
||
|
|
) -> PResult<'a, bool> {
|
||
|
|
if matches!(asm_macro, AsmMacro::Asm) {
|
||
|
|
Ok(p.eat_keyword(exp))
|
||
|
|
} else {
|
||
|
|
let span = p.token.span;
|
||
|
|
if p.eat_keyword_noexpect(exp.kw) {
|
||
|
|
// in gets printed as `r#in` otherwise
|
||
|
|
let symbol = if exp.kw == kw::In { "in" } else { exp.kw.as_str() };
|
||
|
|
Err(p.dcx().create_err(errors::AsmUnsupportedOperand {
|
||
|
|
span,
|
||
|
|
symbol,
|
||
|
|
macro_name: asm_macro.macro_name(),
|
||
|
|
}))
|
||
|
|
} else {
|
||
|
|
Ok(false)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn parse_asm_operand<'a>(
|
||
|
|
p: &mut Parser<'a>,
|
||
|
|
asm_macro: AsmMacro,
|
||
|
|
) -> PResult<'a, Option<ast::InlineAsmOperand>> {
|
||
|
|
let dcx = p.dcx();
|
||
|
|
|
||
|
|
Ok(Some(if eat_operand_keyword(p, exp!(In), asm_macro)? {
|
||
|
|
let reg = parse_reg(p)?;
|
||
|
|
if p.eat_keyword(exp!(Underscore)) {
|
||
|
|
let err = dcx.create_err(errors::AsmUnderscoreInput { span: p.token.span });
|
||
|
|
return Err(err);
|
||
|
|
}
|
||
|
|
let expr = p.parse_expr()?;
|
||
|
|
ast::InlineAsmOperand::In { reg, expr }
|
||
|
|
} else if eat_operand_keyword(p, exp!(Out), asm_macro)? {
|
||
|
|
let reg = parse_reg(p)?;
|
||
|
|
let expr = if p.eat_keyword(exp!(Underscore)) { None } else { Some(p.parse_expr()?) };
|
||
|
|
ast::InlineAsmOperand::Out { reg, expr, late: false }
|
||
|
|
} else if eat_operand_keyword(p, exp!(Lateout), asm_macro)? {
|
||
|
|
let reg = parse_reg(p)?;
|
||
|
|
let expr = if p.eat_keyword(exp!(Underscore)) { None } else { Some(p.parse_expr()?) };
|
||
|
|
ast::InlineAsmOperand::Out { reg, expr, late: true }
|
||
|
|
} else if eat_operand_keyword(p, exp!(Inout), asm_macro)? {
|
||
|
|
let reg = parse_reg(p)?;
|
||
|
|
if p.eat_keyword(exp!(Underscore)) {
|
||
|
|
let err = dcx.create_err(errors::AsmUnderscoreInput { span: p.token.span });
|
||
|
|
return Err(err);
|
||
|
|
}
|
||
|
|
let expr = p.parse_expr()?;
|
||
|
|
if p.eat(exp!(FatArrow)) {
|
||
|
|
let out_expr =
|
||
|
|
if p.eat_keyword(exp!(Underscore)) { None } else { Some(p.parse_expr()?) };
|
||
|
|
ast::InlineAsmOperand::SplitInOut { reg, in_expr: expr, out_expr, late: false }
|
||
|
|
} else {
|
||
|
|
ast::InlineAsmOperand::InOut { reg, expr, late: false }
|
||
|
|
}
|
||
|
|
} else if eat_operand_keyword(p, exp!(Inlateout), asm_macro)? {
|
||
|
|
let reg = parse_reg(p)?;
|
||
|
|
if p.eat_keyword(exp!(Underscore)) {
|
||
|
|
let err = dcx.create_err(errors::AsmUnderscoreInput { span: p.token.span });
|
||
|
|
return Err(err);
|
||
|
|
}
|
||
|
|
let expr = p.parse_expr()?;
|
||
|
|
if p.eat(exp!(FatArrow)) {
|
||
|
|
let out_expr =
|
||
|
|
if p.eat_keyword(exp!(Underscore)) { None } else { Some(p.parse_expr()?) };
|
||
|
|
ast::InlineAsmOperand::SplitInOut { reg, in_expr: expr, out_expr, late: true }
|
||
|
|
} else {
|
||
|
|
ast::InlineAsmOperand::InOut { reg, expr, late: true }
|
||
|
|
}
|
||
|
|
} else if eat_operand_keyword(p, exp!(Label), asm_macro)? {
|
||
|
|
let block = p.parse_block()?;
|
||
|
|
ast::InlineAsmOperand::Label { block }
|
||
|
|
} else if p.eat_keyword(exp!(Const)) {
|
||
|
|
let anon_const = p.parse_expr_anon_const()?;
|
||
|
|
ast::InlineAsmOperand::Const { anon_const }
|
||
|
|
} else if p.eat_keyword(exp!(Sym)) {
|
||
|
|
let expr = p.parse_expr()?;
|
||
|
|
let ast::ExprKind::Path(qself, path) = &expr.kind else {
|
||
|
|
let err = dcx.create_err(errors::AsmSymNoPath { span: expr.span });
|
||
|
|
return Err(err);
|
||
|
|
};
|
||
|
|
let sym =
|
||
|
|
ast::InlineAsmSym { id: ast::DUMMY_NODE_ID, qself: qself.clone(), path: path.clone() };
|
||
|
|
ast::InlineAsmOperand::Sym { sym }
|
||
|
|
} else {
|
||
|
|
return Ok(None);
|
||
|
|
}))
|
||
|
|
}
|
||
|
|
|
||
|
|
// Public for rustfmt.
|
||
|
|
pub fn parse_asm_args<'a>(
|
||
|
|
p: &mut Parser<'a>,
|
||
|
|
sp: Span,
|
||
|
|
asm_macro: AsmMacro,
|
||
|
|
) -> PResult<'a, Vec<AsmArg>> {
|
||
|
|
let dcx = p.dcx();
|
||
|
|
|
||
|
|
if p.token == token::Eof {
|
||
|
|
return Err(dcx.create_err(errors::AsmRequiresTemplate { span: sp }));
|
||
|
|
}
|
||
|
|
|
||
|
|
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),
|
||
|
|
attributes,
|
||
|
|
});
|
||
|
|
|
||
|
|
let mut allow_templates = true;
|
||
|
|
|
||
|
|
while p.token != token::Eof {
|
||
|
|
if !p.eat(exp!(Comma)) {
|
||
|
|
if allow_templates {
|
||
|
|
// After a template string, we always expect *only* a comma...
|
||
|
|
return Err(dcx.create_err(errors::AsmExpectedComma { span: p.token.span }));
|
||
|
|
} else {
|
||
|
|
// ...after that delegate to `expect` to also include the other expected tokens.
|
||
|
|
return Err(p.expect(exp!(Comma)).err().unwrap());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Accept trailing commas.
|
||
|
|
if p.token == token::Eof {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
let attributes = AsmAttrVec::parse(p)?;
|
||
|
|
let span_start = p.token.span;
|
||
|
|
|
||
|
|
// Parse `clobber_abi`.
|
||
|
|
if p.eat_keyword(exp!(ClobberAbi)) {
|
||
|
|
allow_templates = false;
|
||
|
|
|
||
|
|
args.push(AsmArg {
|
||
|
|
kind: AsmArgKind::ClobberAbi(parse_clobber_abi(p)?),
|
||
|
|
span: span_start.to(p.prev_token.span),
|
||
|
|
attributes,
|
||
|
|
});
|
||
|
|
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse `options`.
|
||
|
|
if p.eat_keyword(exp!(Options)) {
|
||
|
|
allow_templates = false;
|
||
|
|
|
||
|
|
args.push(AsmArg {
|
||
|
|
kind: AsmArgKind::Options(parse_options(p, asm_macro)?),
|
||
|
|
span: span_start.to(p.prev_token.span),
|
||
|
|
attributes,
|
||
|
|
});
|
||
|
|
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse operand names.
|
||
|
|
let name = if p.token.is_ident() && p.look_ahead(1, |t| *t == token::Eq) {
|
||
|
|
let (ident, _) = p.token.ident().unwrap();
|
||
|
|
p.bump();
|
||
|
|
p.expect(exp!(Eq))?;
|
||
|
|
allow_templates = false;
|
||
|
|
Some(ident.name)
|
||
|
|
} else {
|
||
|
|
None
|
||
|
|
};
|
||
|
|
|
||
|
|
if let Some(op) = parse_asm_operand(p, asm_macro)? {
|
||
|
|
allow_templates = false;
|
||
|
|
|
||
|
|
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()?;
|
||
|
|
// If it can't possibly expand to a string, provide diagnostics here to include other
|
||
|
|
// things it could have been.
|
||
|
|
match template.kind {
|
||
|
|
ast::ExprKind::Lit(token_lit)
|
||
|
|
if matches!(
|
||
|
|
token_lit.kind,
|
||
|
|
token::LitKind::Str | token::LitKind::StrRaw(_)
|
||
|
|
) => {}
|
||
|
|
ast::ExprKind::MacCall(..) => {}
|
||
|
|
_ => {
|
||
|
|
let err = dcx.create_err(errors::AsmExpectedOther {
|
||
|
|
span: template.span,
|
||
|
|
is_inline_asm: matches!(asm_macro, AsmMacro::Asm),
|
||
|
|
});
|
||
|
|
return Err(err);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
args.push(AsmArg {
|
||
|
|
span: template.span,
|
||
|
|
kind: AsmArgKind::Template(template),
|
||
|
|
attributes,
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
p.unexpected_any()?
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Ok(args)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn parse_options<'a>(p: &mut Parser<'a>, asm_macro: AsmMacro) -> PResult<'a, Vec<AsmOption>> {
|
||
|
|
p.expect(exp!(OpenParen))?;
|
||
|
|
|
||
|
|
let mut asm_options = Vec::new();
|
||
|
|
|
||
|
|
while !p.eat(exp!(CloseParen)) {
|
||
|
|
const OPTIONS: [(ExpKeywordPair, ast::InlineAsmOptions); ast::InlineAsmOptions::COUNT] = [
|
||
|
|
(exp!(Pure), ast::InlineAsmOptions::PURE),
|
||
|
|
(exp!(Nomem), ast::InlineAsmOptions::NOMEM),
|
||
|
|
(exp!(Readonly), ast::InlineAsmOptions::READONLY),
|
||
|
|
(exp!(PreservesFlags), ast::InlineAsmOptions::PRESERVES_FLAGS),
|
||
|
|
(exp!(Noreturn), ast::InlineAsmOptions::NORETURN),
|
||
|
|
(exp!(Nostack), ast::InlineAsmOptions::NOSTACK),
|
||
|
|
(exp!(MayUnwind), ast::InlineAsmOptions::MAY_UNWIND),
|
||
|
|
(exp!(AttSyntax), ast::InlineAsmOptions::ATT_SYNTAX),
|
||
|
|
(exp!(Raw), ast::InlineAsmOptions::RAW),
|
||
|
|
];
|
||
|
|
|
||
|
|
'blk: {
|
||
|
|
for (exp, options) in OPTIONS {
|
||
|
|
// Gives a more accurate list of expected next tokens.
|
||
|
|
let kw_matched = if asm_macro.is_supported_option(options) {
|
||
|
|
p.eat_keyword(exp)
|
||
|
|
} else {
|
||
|
|
p.eat_keyword_noexpect(exp.kw)
|
||
|
|
};
|
||
|
|
|
||
|
|
if kw_matched {
|
||
|
|
let span = p.prev_token.span;
|
||
|
|
let span_with_comma =
|
||
|
|
if p.token == token::Comma { span.to(p.token.span) } else { span };
|
||
|
|
|
||
|
|
asm_options.push(AsmOption { symbol: exp.kw, span, options, span_with_comma });
|
||
|
|
break 'blk;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return p.unexpected_any();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Allow trailing commas.
|
||
|
|
if p.eat(exp!(CloseParen)) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
p.expect(exp!(Comma))?;
|
||
|
|
}
|
||
|
|
|
||
|
|
Ok(asm_options)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn parse_clobber_abi<'a>(p: &mut Parser<'a>) -> PResult<'a, Vec<(Symbol, Span)>> {
|
||
|
|
p.expect(exp!(OpenParen))?;
|
||
|
|
|
||
|
|
if p.eat(exp!(CloseParen)) {
|
||
|
|
return Err(p.dcx().create_err(errors::NonABI { span: p.token.span }));
|
||
|
|
}
|
||
|
|
|
||
|
|
let mut new_abis = Vec::new();
|
||
|
|
while !p.eat(exp!(CloseParen)) {
|
||
|
|
match p.parse_str_lit() {
|
||
|
|
Ok(str_lit) => {
|
||
|
|
new_abis.push((str_lit.symbol_unescaped, str_lit.span));
|
||
|
|
}
|
||
|
|
Err(opt_lit) => {
|
||
|
|
let span = opt_lit.map_or(p.token.span, |lit| lit.span);
|
||
|
|
return Err(p.dcx().create_err(errors::AsmExpectedStringLiteral { span }));
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Allow trailing commas
|
||
|
|
if p.eat(exp!(CloseParen)) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
p.expect(exp!(Comma))?;
|
||
|
|
}
|
||
|
|
|
||
|
|
Ok(new_abis)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn parse_reg<'a>(p: &mut Parser<'a>) -> PResult<'a, ast::InlineAsmRegOrRegClass> {
|
||
|
|
p.expect(exp!(OpenParen))?;
|
||
|
|
let result = match p.token.uninterpolate().kind {
|
||
|
|
token::Ident(name, IdentIsRaw::No) => ast::InlineAsmRegOrRegClass::RegClass(name),
|
||
|
|
token::Literal(token::Lit { kind: token::LitKind::Str, symbol, suffix: _ }) => {
|
||
|
|
ast::InlineAsmRegOrRegClass::Reg(symbol)
|
||
|
|
}
|
||
|
|
_ => {
|
||
|
|
return Err(p.dcx().create_err(errors::ExpectedRegisterClassOrExplicitRegister {
|
||
|
|
span: p.token.span,
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
};
|
||
|
|
p.bump();
|
||
|
|
p.expect(exp!(CloseParen))?;
|
||
|
|
Ok(result)
|
||
|
|
}
|