| use rustc_ast::{self as ast, AsmMacro}; | |
| use rustc_span::{Span, Symbol, kw}; | |
| use super::{ExpKeywordPair, ForceCollect, IdentKind, Trailing, UsePreAttrPos}; | |
| use crate::{PResult, Parser, diagnostics, 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(Box<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 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(diagnostics::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(diagnostics::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(diagnostics::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(diagnostics::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(diagnostics::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(diagnostics::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(diagnostics::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(diagnostics::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(diagnostics::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(diagnostics::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, IdentKind::Normal) => ast::InlineAsmRegOrRegClass::RegClass(name), | |
| token::Literal(token::Lit { kind: token::LitKind::Str, symbol, suffix: _ }) => { | |
| ast::InlineAsmRegOrRegClass::Reg(symbol) | |
| } | |
| _ => { | |
| return Err(p.dcx().create_err(diagnostics::ExpectedRegisterClassOrExplicitRegister { | |
| span: p.token.span, | |
| })); | |
| } | |
| }; | |
| p.bump(); | |
| p.expect(exp!(CloseParen))?; | |
| Ok(result) | |
| } |