From d3157c9529359531238bf8508d059e4c4c49cf75 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 29 Apr 2025 20:25:59 +0200 Subject: [PATCH 01/10] determine later whether an explicit reg was used --- compiler/rustc_builtin_macros/src/asm.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index 3e8ddb8abd43f..58aba3e590323 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -135,9 +135,8 @@ pub fn parse_asm_args<'a>( None }; - let mut explicit_reg = false; let op = if eat_operand_keyword(p, exp!(In), asm_macro)? { - let reg = parse_reg(p, &mut explicit_reg)?; + 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); @@ -145,15 +144,15 @@ pub fn parse_asm_args<'a>( 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, &mut explicit_reg)?; + 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, &mut explicit_reg)?; + 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, &mut explicit_reg)?; + 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); @@ -167,7 +166,7 @@ pub fn parse_asm_args<'a>( ast::InlineAsmOperand::InOut { reg, expr, late: false } } } else if eat_operand_keyword(p, exp!(Inlateout), asm_macro)? { - let reg = parse_reg(p, &mut explicit_reg)?; + 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); @@ -223,6 +222,8 @@ pub fn parse_asm_args<'a>( p.unexpected_any()? }; + let explicit_reg = matches!(op.reg(), Some(ast::InlineAsmRegOrRegClass::Reg(_))); + allow_templates = false; let span = span_start.to(p.prev_token.span); let slot = args.operands.len(); @@ -231,6 +232,7 @@ pub fn parse_asm_args<'a>( // Validate the order of named, positional & explicit register operands and // clobber_abi/options. We do this at the end once we have the full span // of the argument available. + if explicit_reg { if name.is_some() { dcx.emit_err(errors::AsmExplicitRegisterName { span }); @@ -478,15 +480,11 @@ fn parse_clobber_abi<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> PResult<'a, Ok(()) } -fn parse_reg<'a>( - p: &mut Parser<'a>, - explicit_reg: &mut bool, -) -> PResult<'a, ast::InlineAsmRegOrRegClass> { +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: _ }) => { - *explicit_reg = true; ast::InlineAsmRegOrRegClass::Reg(symbol) } _ => { From 408cfef94794b332593168fa20abd2216a062b99 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 29 Apr 2025 20:35:39 +0200 Subject: [PATCH 02/10] extract operand parser --- compiler/rustc_builtin_macros/src/asm.rs | 176 ++++++++++++----------- 1 file changed, 93 insertions(+), 83 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index 58aba3e590323..3afa2d3dd8eec 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -69,6 +69,76 @@ fn parse_args<'a>( parse_asm_args(&mut p, sp, asm_macro) } +fn parse_asm_operand<'a>( + p: &mut Parser<'a>, + asm_macro: AsmMacro, +) -> PResult<'a, Option> { + 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); + })) +} + // Primarily public for rustfmt consumption. // Internal consumers should continue to leverage `expand_asm`/`expand__global_asm` pub fn parse_asm_args<'a>( @@ -135,91 +205,31 @@ pub fn parse_asm_args<'a>( None }; - let op = 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 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); + let Some(op) = parse_asm_operand(p, asm_macro)? 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.templates.push(template); + continue; + } else { + p.unexpected_any()? } - args.templates.push(template); - continue; - } else { - p.unexpected_any()? }; let explicit_reg = matches!(op.reg(), Some(ast::InlineAsmRegOrRegClass::Reg(_))); From fbc7181b8c0ffc9b84d0c68c6cbb747eba081fcd Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 29 Apr 2025 22:43:46 +0200 Subject: [PATCH 03/10] a new parser generating the exact same error messages --- compiler/rustc_builtin_macros/src/asm.rs | 287 ++++++++++++++++++++--- 1 file changed, 248 insertions(+), 39 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index 3afa2d3dd8eec..3053d08b65c98 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -18,6 +18,21 @@ use {rustc_ast as ast, rustc_parse_format as parse}; use crate::errors; use crate::util::{ExprToSpannedString, expr_to_spanned_string}; +pub struct RawAsmArgs(Vec); + +pub struct RawAsmArg { + pub span: Span, + pub attributes: ast::AttrVec, + pub kind: RawAsmArgKind, +} + +enum RawAsmArgKind { + Template(P), + Operand(Option, ast::InlineAsmOperand), + Options(Vec<(Symbol, ast::InlineAsmOptions, Span, Span)>), + ClobberAbi(Vec<(Symbol, Span)>), +} + pub struct AsmArgs { pub templates: Vec>, pub operands: Vec<(ast::InlineAsmOperand, Span)>, @@ -139,31 +154,28 @@ fn parse_asm_operand<'a>( })) } -// Primarily public for rustfmt consumption. -// Internal consumers should continue to leverage `expand_asm`/`expand__global_asm` -pub fn parse_asm_args<'a>( +pub fn parse_raw_asm_args<'a>( p: &mut Parser<'a>, sp: Span, asm_macro: AsmMacro, -) -> PResult<'a, AsmArgs> { +) -> PResult<'a, Vec> { let dcx = p.dcx(); if p.token == token::Eof { return Err(dcx.create_err(errors::AsmRequiresTemplate { span: sp })); } + let mut args = Vec::new(); + let first_template = p.parse_expr()?; - let mut args = AsmArgs { - templates: vec![first_template], - operands: vec![], - named_args: Default::default(), - reg_args: Default::default(), - clobber_abis: Vec::new(), - options: ast::InlineAsmOptions::empty(), - options_spans: vec![], - }; + args.push(RawAsmArg { + span: first_template.span, + attributes: ast::AttrVec::new(), + kind: RawAsmArgKind::Template(first_template), + }); let mut allow_templates = true; + while p.token != token::Eof { if !p.eat(exp!(Comma)) { if allow_templates { @@ -178,22 +190,116 @@ pub fn parse_asm_args<'a>( break; } // accept trailing commas + let span_start = p.token.span; + // Parse clobber_abi if p.eat_keyword(exp!(ClobberAbi)) { - parse_clobber_abi(p, &mut args)?; allow_templates = false; + + p.expect(exp!(OpenParen))?; + + // FIXME: why not allow this? + 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))?; + } + + args.push(RawAsmArg { + span: span_start.to(p.prev_token.span), + attributes: ast::AttrVec::new(), + kind: RawAsmArgKind::ClobberAbi(new_abis), + }); + continue; } // Parse options if p.eat_keyword(exp!(Options)) { - parse_options(p, &mut args, asm_macro)?; allow_templates = false; + + p.expect(exp!(OpenParen))?; + + let mut 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, option) in OPTIONS { + let kw_matched = if asm_macro.is_supported_option(option) { + p.eat_keyword(exp) + } else { + p.eat_keyword_noexpect(exp.kw) + }; + + if kw_matched { + let span = p.prev_token.span; + let full_span = + if p.token == token::Comma { span.to(p.token.span) } else { span }; + + if !asm_macro.is_supported_option(option) { + // Tool-only output + p.dcx().emit_err(errors::AsmUnsupportedOption { + span, + symbol: exp.kw, + full_span, + macro_name: asm_macro.macro_name(), + }); + } + + options.push((exp.kw, option, span, full_span)); + break 'blk; + } + } + + return p.unexpected_any(); + } + + // Allow trailing commas + if p.eat(exp!(CloseParen)) { + break; + } + p.expect(exp!(Comma))?; + } + + args.push(RawAsmArg { + span: span_start.to(p.prev_token.span), + attributes: ast::AttrVec::new(), + kind: RawAsmArgKind::Options(options), + }); + continue; } - let span_start = p.token.span; - // Parse operand names let name = if p.token.is_ident() && p.look_ahead(1, |t| *t == token::Eq) { let (ident, _) = p.token.ident().unwrap(); @@ -225,40 +331,143 @@ pub fn parse_asm_args<'a>( return Err(err); } } - args.templates.push(template); + + args.push(RawAsmArg { + span: template.span, + attributes: ast::AttrVec::new(), + kind: RawAsmArgKind::Template(template), + }); + continue; } else { p.unexpected_any()? } }; - let explicit_reg = matches!(op.reg(), Some(ast::InlineAsmRegOrRegClass::Reg(_))); - allow_templates = false; - let span = span_start.to(p.prev_token.span); - let slot = args.operands.len(); - args.operands.push((op, span)); - // Validate the order of named, positional & explicit register operands and - // clobber_abi/options. We do this at the end once we have the full span - // of the argument available. + args.push(RawAsmArg { + span: span_start.to(p.prev_token.span), + attributes: ast::AttrVec::new(), + kind: RawAsmArgKind::Operand(name, op), + }); + } + + Ok(args) +} + +pub fn parse_asm_args<'a>( + p: &mut Parser<'a>, + sp: Span, + asm_macro: AsmMacro, +) -> PResult<'a, AsmArgs> { + let dcx = p.dcx(); + + let mut args = AsmArgs { + templates: vec![], + operands: vec![], + named_args: Default::default(), + reg_args: Default::default(), + clobber_abis: Vec::new(), + options: ast::InlineAsmOptions::empty(), + options_spans: vec![], + }; - if explicit_reg { - if name.is_some() { - dcx.emit_err(errors::AsmExplicitRegisterName { span }); + let mut allow_templates = true; + + for arg in parse_raw_asm_args(p, sp, asm_macro)? { + match arg.kind { + RawAsmArgKind::Template(template) => { + if allow_templates { + args.templates.push(template); + } else { + 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.templates.push(template); + } } - args.reg_args.insert(slot); - } else if let Some(name) = name { - if let Some(&prev) = args.named_args.get(&name) { - dcx.emit_err(errors::AsmDuplicateArg { span, name, prev: args.operands[prev].1 }); - continue; + RawAsmArgKind::Operand(name, op) => { + allow_templates = false; + + let explicit_reg = matches!(op.reg(), Some(ast::InlineAsmRegOrRegClass::Reg(_))); + + let span = arg.span; + let slot = args.operands.len(); + args.operands.push((op, span)); + + // Validate the order of named, positional & explicit register operands and + // clobber_abi/options. We do this at the end once we have the full span + // of the argument available. + + if explicit_reg { + if name.is_some() { + dcx.emit_err(errors::AsmExplicitRegisterName { span }); + } + args.reg_args.insert(slot); + } else if let Some(name) = name { + if let Some(&prev) = args.named_args.get(&name) { + dcx.emit_err(errors::AsmDuplicateArg { + span, + name, + prev: args.operands[prev].1, + }); + continue; + } + args.named_args.insert(name, slot); + } else if !args.named_args.is_empty() || !args.reg_args.is_empty() { + let named = args.named_args.values().map(|p| args.operands[*p].1).collect(); + let explicit = args.reg_args.iter().map(|p| args.operands[p].1).collect(); + + dcx.emit_err(errors::AsmPositionalAfter { span, named, explicit }); + } } - args.named_args.insert(name, slot); - } else if !args.named_args.is_empty() || !args.reg_args.is_empty() { - let named = args.named_args.values().map(|p| args.operands[*p].1).collect(); - let explicit = args.reg_args.iter().map(|p| args.operands[p].1).collect(); + RawAsmArgKind::Options(new_options) => { + allow_templates = false; + + for (symbol, option, span, full_span) in new_options { + if !asm_macro.is_supported_option(option) { + /* + // Tool-only output + p.dcx().emit_err(errors::AsmUnsupportedOption { + span, + symbol, + full_span, + macro_name: asm_macro.macro_name(), + }); + */ + } else if args.options.contains(option) { + // Tool-only output + p.dcx().emit_err(errors::AsmOptAlreadyprovided { span, symbol, full_span }); + } else { + args.options |= option; + } + } - dcx.emit_err(errors::AsmPositionalAfter { span, named, explicit }); + args.options_spans.push(arg.span); + } + RawAsmArgKind::ClobberAbi(new_abis) => { + allow_templates = false; + + match &new_abis[..] { + // should have errored above during parsing + [] => unreachable!(), + [(abi, _span)] => args.clobber_abis.push((*abi, arg.span)), + _ => args.clobber_abis.extend(new_abis), + } + } } } From 61a9db7e454369caa91675c4adae929aa84a3151 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 29 Apr 2025 23:03:28 +0200 Subject: [PATCH 04/10] cleanup `parse_clobber_abi` --- compiler/rustc_builtin_macros/src/asm.rs | 50 +++--------------------- 1 file changed, 5 insertions(+), 45 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index 3053d08b65c98..1fcac5e2f8f29 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -196,36 +196,10 @@ pub fn parse_raw_asm_args<'a>( if p.eat_keyword(exp!(ClobberAbi)) { allow_templates = false; - p.expect(exp!(OpenParen))?; - - // FIXME: why not allow this? - 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))?; - } - args.push(RawAsmArg { - span: span_start.to(p.prev_token.span), attributes: ast::AttrVec::new(), - kind: RawAsmArgKind::ClobberAbi(new_abis), + kind: RawAsmArgKind::ClobberAbi(parse_clobber_abi(p)?), + span: span_start.to(p.prev_token.span), }); continue; @@ -655,11 +629,10 @@ fn parse_options<'a>( Ok(()) } -fn parse_clobber_abi<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> PResult<'a, ()> { - let span_start = p.prev_token.span; - +fn parse_clobber_abi<'a>(p: &mut Parser<'a>) -> PResult<'a, Vec<(Symbol, Span)>> { p.expect(exp!(OpenParen))?; + // FIXME: why not allow this? if p.eat(exp!(CloseParen)) { return Err(p.dcx().create_err(errors::NonABI { span: p.token.span })); } @@ -683,20 +656,7 @@ fn parse_clobber_abi<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> PResult<'a, p.expect(exp!(Comma))?; } - let full_span = span_start.to(p.prev_token.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(()) + Ok(new_abis) } fn parse_reg<'a>(p: &mut Parser<'a>) -> PResult<'a, ast::InlineAsmRegOrRegClass> { From e15b0f6a8cd221155fc7016d1925b334cc866cfe Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 29 Apr 2025 23:05:50 +0200 Subject: [PATCH 05/10] cleanup `parse_options` --- compiler/rustc_builtin_macros/src/asm.rs | 90 ++++++------------------ 1 file changed, 22 insertions(+), 68 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index 1fcac5e2f8f29..12d3b6222c018 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -209,66 +209,10 @@ pub fn parse_raw_asm_args<'a>( if p.eat_keyword(exp!(Options)) { allow_templates = false; - p.expect(exp!(OpenParen))?; - - let mut 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, option) in OPTIONS { - let kw_matched = if asm_macro.is_supported_option(option) { - p.eat_keyword(exp) - } else { - p.eat_keyword_noexpect(exp.kw) - }; - - if kw_matched { - let span = p.prev_token.span; - let full_span = - if p.token == token::Comma { span.to(p.token.span) } else { span }; - - if !asm_macro.is_supported_option(option) { - // Tool-only output - p.dcx().emit_err(errors::AsmUnsupportedOption { - span, - symbol: exp.kw, - full_span, - macro_name: asm_macro.macro_name(), - }); - } - - options.push((exp.kw, option, span, full_span)); - break 'blk; - } - } - - return p.unexpected_any(); - } - - // Allow trailing commas - if p.eat(exp!(CloseParen)) { - break; - } - p.expect(exp!(Comma))?; - } - args.push(RawAsmArg { - span: span_start.to(p.prev_token.span), attributes: ast::AttrVec::new(), - kind: RawAsmArgKind::Options(options), + kind: RawAsmArgKind::Options(parse_options(p, asm_macro)?), + span: span_start.to(p.prev_token.span), }); continue; @@ -579,13 +523,12 @@ fn try_set_option<'a>( fn parse_options<'a>( p: &mut Parser<'a>, - args: &mut AsmArgs, asm_macro: AsmMacro, -) -> PResult<'a, ()> { - let span_start = p.prev_token.span; - +) -> PResult<'a, Vec<(Symbol, ast::InlineAsmOptions, Span, Span)>> { p.expect(exp!(OpenParen))?; + let mut options = Vec::new(); + while !p.eat(exp!(CloseParen)) { const OPTIONS: [(ExpKeywordPair, ast::InlineAsmOptions); ast::InlineAsmOptions::COUNT] = [ (exp!(Pure), ast::InlineAsmOptions::PURE), @@ -608,12 +551,26 @@ fn parse_options<'a>( }; if kw_matched { - try_set_option(p, args, asm_macro, exp.kw, option); + let span = p.prev_token.span; + let full_span = + if p.token == token::Comma { span.to(p.token.span) } else { span }; + + if !asm_macro.is_supported_option(option) { + // Tool-only output + p.dcx().emit_err(errors::AsmUnsupportedOption { + span, + symbol: exp.kw, + full_span, + macro_name: asm_macro.macro_name(), + }); + } + + options.push((exp.kw, option, span, full_span)); break 'blk; } } - return p.unexpected(); + return p.unexpected_any(); } // Allow trailing commas @@ -623,10 +580,7 @@ fn parse_options<'a>( p.expect(exp!(Comma))?; } - let new_span = span_start.to(p.prev_token.span); - args.options_spans.push(new_span); - - Ok(()) + Ok(options) } fn parse_clobber_abi<'a>(p: &mut Parser<'a>) -> PResult<'a, Vec<(Symbol, Span)>> { From f39a13a7b4e84533329d42507438666d56d0a38f Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 29 Apr 2025 23:06:52 +0200 Subject: [PATCH 06/10] cleanup unused code --- compiler/rustc_builtin_macros/src/asm.rs | 50 +----------------------- 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index 12d3b6222c018..aed6cecd7b16f 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -18,15 +18,13 @@ use {rustc_ast as ast, rustc_parse_format as parse}; use crate::errors; use crate::util::{ExprToSpannedString, expr_to_spanned_string}; -pub struct RawAsmArgs(Vec); - pub struct RawAsmArg { pub span: Span, pub attributes: ast::AttrVec, pub kind: RawAsmArgKind, } -enum RawAsmArgKind { +pub enum RawAsmArgKind { Template(P), Operand(Option, ast::InlineAsmOperand), Options(Vec<(Symbol, ast::InlineAsmOptions, Span, Span)>), @@ -475,52 +473,6 @@ pub fn parse_asm_args<'a>( Ok(args) } -/// Report a duplicate option error. -/// -/// This function must be called immediately after the option token is parsed. -/// Otherwise, the suggestion will be incorrect. -fn err_duplicate_option(p: &Parser<'_>, symbol: Symbol, span: Span) { - // Tool-only output - let full_span = if p.token == token::Comma { span.to(p.token.span) } else { span }; - p.dcx().emit_err(errors::AsmOptAlreadyprovided { span, symbol, full_span }); -} - -/// Report an invalid option error. -/// -/// This function must be called immediately after the option token is parsed. -/// Otherwise, the suggestion will be incorrect. -fn err_unsupported_option(p: &Parser<'_>, asm_macro: AsmMacro, symbol: Symbol, span: Span) { - // Tool-only output - let full_span = if p.token == token::Comma { span.to(p.token.span) } else { span }; - p.dcx().emit_err(errors::AsmUnsupportedOption { - span, - symbol, - full_span, - macro_name: asm_macro.macro_name(), - }); -} - -/// Try to set the provided option in the provided `AsmArgs`. -/// If it is already set, report a duplicate option error. -/// -/// This function must be called immediately after the option token is parsed. -/// Otherwise, the error will not point to the correct spot. -fn try_set_option<'a>( - p: &Parser<'a>, - args: &mut AsmArgs, - asm_macro: AsmMacro, - symbol: Symbol, - option: ast::InlineAsmOptions, -) { - if !asm_macro.is_supported_option(option) { - err_unsupported_option(p, asm_macro, symbol, p.prev_token.span); - } else if args.options.contains(option) { - err_duplicate_option(p, symbol, p.prev_token.span); - } else { - args.options |= option; - } -} - fn parse_options<'a>( p: &mut Parser<'a>, asm_macro: AsmMacro, From 2860b75fa405cc5d42a9f72fdecbc63241f1221d Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 29 Apr 2025 23:33:14 +0200 Subject: [PATCH 07/10] more cleanup --- compiler/rustc_builtin_macros/src/asm.rs | 122 +++++++++++------------ 1 file changed, 60 insertions(+), 62 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index aed6cecd7b16f..1e8be9a66b585 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -4,7 +4,7 @@ use rustc_ast::ptr::P; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AsmMacro, token}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; -use rustc_errors::PResult; +use rustc_errors::{DiagCtxtHandle, PResult}; use rustc_expand::base::*; use rustc_index::bit_set::GrowableBitSet; use rustc_parse::exp; @@ -18,10 +18,11 @@ use {rustc_ast as ast, rustc_parse_format as parse}; use crate::errors; use crate::util::{ExprToSpannedString, expr_to_spanned_string}; +/// An argument to one of the `asm!` macros. The argument is syntactically valid, but is otherwise +/// not validated at all. pub struct RawAsmArg { - pub span: Span, - pub attributes: ast::AttrVec, pub kind: RawAsmArgKind, + pub span: Span, } pub enum RawAsmArgKind { @@ -31,6 +32,7 @@ pub enum RawAsmArgKind { ClobberAbi(Vec<(Symbol, Span)>), } +/// Validated assembly arguments, ready for macro expansion. pub struct AsmArgs { pub templates: Vec>, pub operands: Vec<(ast::InlineAsmOperand, Span)>, @@ -72,16 +74,6 @@ fn eat_operand_keyword<'a>( } } -fn parse_args<'a>( - ecx: &ExtCtxt<'a>, - sp: Span, - tts: TokenStream, - asm_macro: AsmMacro, -) -> PResult<'a, AsmArgs> { - let mut p = ecx.new_parser_from_tts(tts); - parse_asm_args(&mut p, sp, asm_macro) -} - fn parse_asm_operand<'a>( p: &mut Parser<'a>, asm_macro: AsmMacro, @@ -168,7 +160,6 @@ pub fn parse_raw_asm_args<'a>( let first_template = p.parse_expr()?; args.push(RawAsmArg { span: first_template.span, - attributes: ast::AttrVec::new(), kind: RawAsmArgKind::Template(first_template), }); @@ -195,7 +186,6 @@ pub fn parse_raw_asm_args<'a>( allow_templates = false; args.push(RawAsmArg { - attributes: ast::AttrVec::new(), kind: RawAsmArgKind::ClobberAbi(parse_clobber_abi(p)?), span: span_start.to(p.prev_token.span), }); @@ -208,7 +198,6 @@ pub fn parse_raw_asm_args<'a>( allow_templates = false; args.push(RawAsmArg { - attributes: ast::AttrVec::new(), kind: RawAsmArgKind::Options(parse_options(p, asm_macro)?), span: span_start.to(p.prev_token.span), }); @@ -227,58 +216,68 @@ pub fn parse_raw_asm_args<'a>( None }; - let Some(op) = parse_asm_operand(p, asm_macro)? 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(RawAsmArg { - span: template.span, - attributes: ast::AttrVec::new(), - kind: RawAsmArgKind::Template(template), - }); + if let Some(op) = parse_asm_operand(p, asm_macro)? { + allow_templates = false; - continue; - } else { - p.unexpected_any()? + args.push(RawAsmArg { + span: span_start.to(p.prev_token.span), + kind: RawAsmArgKind::Operand(name, op), + }); + } 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); + } } - }; - allow_templates = false; - - args.push(RawAsmArg { - span: span_start.to(p.prev_token.span), - attributes: ast::AttrVec::new(), - kind: RawAsmArgKind::Operand(name, op), - }); + args.push(RawAsmArg { span: template.span, kind: RawAsmArgKind::Template(template) }); + } else { + p.unexpected_any()? + } } Ok(args) } +fn parse_args<'a>( + ecx: &ExtCtxt<'a>, + sp: Span, + tts: TokenStream, + asm_macro: AsmMacro, +) -> PResult<'a, AsmArgs> { + let mut p = ecx.new_parser_from_tts(tts); + parse_asm_args(&mut p, sp, asm_macro) +} + +// public for use in rustfmt +// FIXME: use `RawAsmArg` in the formatting code instead. pub fn parse_asm_args<'a>( p: &mut Parser<'a>, sp: Span, asm_macro: AsmMacro, ) -> PResult<'a, AsmArgs> { - let dcx = p.dcx(); + let raw_args = parse_raw_asm_args(p, sp, asm_macro)?; + validate_raw_asm_args(p.dcx(), asm_macro, raw_args) +} +pub fn validate_raw_asm_args<'a>( + dcx: DiagCtxtHandle<'a>, + asm_macro: AsmMacro, + raw_args: Vec, +) -> PResult<'a, AsmArgs> { let mut args = AsmArgs { templates: vec![], operands: vec![], @@ -291,12 +290,11 @@ pub fn parse_asm_args<'a>( let mut allow_templates = true; - for arg in parse_raw_asm_args(p, sp, asm_macro)? { + for arg in raw_args { match arg.kind { RawAsmArgKind::Template(template) => { - if allow_templates { - args.templates.push(template); - } else { + // The error for the first template is delayed. + if !allow_templates { match template.kind { ast::ExprKind::Lit(token_lit) if matches!( @@ -312,14 +310,14 @@ pub fn parse_asm_args<'a>( return Err(err); } } - args.templates.push(template); } + + args.templates.push(template); } RawAsmArgKind::Operand(name, op) => { allow_templates = false; let explicit_reg = matches!(op.reg(), Some(ast::InlineAsmRegOrRegClass::Reg(_))); - let span = arg.span; let slot = args.operands.len(); args.operands.push((op, span)); @@ -366,7 +364,7 @@ pub fn parse_asm_args<'a>( */ } else if args.options.contains(option) { // Tool-only output - p.dcx().emit_err(errors::AsmOptAlreadyprovided { span, symbol, full_span }); + dcx.emit_err(errors::AsmOptAlreadyprovided { span, symbol, full_span }); } else { args.options |= option; } @@ -496,6 +494,7 @@ fn parse_options<'a>( 'blk: { for (exp, option) in OPTIONS { + // Gives a more accurate list of expected next tokens. let kw_matched = if asm_macro.is_supported_option(option) { p.eat_keyword(exp) } else { @@ -538,7 +537,6 @@ fn parse_options<'a>( fn parse_clobber_abi<'a>(p: &mut Parser<'a>) -> PResult<'a, Vec<(Symbol, Span)>> { p.expect(exp!(OpenParen))?; - // FIXME: why not allow this? if p.eat(exp!(CloseParen)) { return Err(p.dcx().create_err(errors::NonABI { span: p.token.span })); } From 7935e879bed1e6f1893533b50a5e8509f215254d Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 29 Apr 2025 23:39:10 +0200 Subject: [PATCH 08/10] leave a note about maybe delaying an error message --- compiler/rustc_builtin_macros/src/asm.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index 1e8be9a66b585..d7ba726f59d9a 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -353,15 +353,7 @@ pub fn validate_raw_asm_args<'a>( for (symbol, option, span, full_span) in new_options { if !asm_macro.is_supported_option(option) { - /* - // Tool-only output - p.dcx().emit_err(errors::AsmUnsupportedOption { - span, - symbol, - full_span, - macro_name: asm_macro.macro_name(), - }); - */ + // Currently handled during parsing. } else if args.options.contains(option) { // Tool-only output dcx.emit_err(errors::AsmOptAlreadyprovided { span, symbol, full_span }); @@ -506,6 +498,7 @@ fn parse_options<'a>( let full_span = if p.token == token::Comma { span.to(p.token.span) } else { span }; + // NOTE: should this be handled during validation instead? if !asm_macro.is_supported_option(option) { // Tool-only output p.dcx().emit_err(errors::AsmUnsupportedOption { From d30124aa143e02c8c10715032fcc20601d3af5ee Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 30 Apr 2025 01:06:38 +0200 Subject: [PATCH 09/10] attempt to have rustfmt use the new logic apparently it doesn't really use the asm parsing at present, so this may work? --- compiler/rustc_builtin_macros/src/asm.rs | 26 ++++++++--------------- src/tools/rustfmt/src/parse/macros/asm.rs | 9 +++++--- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index d7ba726f59d9a..c8000bbe70b6f 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -4,7 +4,7 @@ use rustc_ast::ptr::P; use rustc_ast::tokenstream::TokenStream; use rustc_ast::{AsmMacro, token}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; -use rustc_errors::{DiagCtxtHandle, PResult}; +use rustc_errors::PResult; use rustc_expand::base::*; use rustc_index::bit_set::GrowableBitSet; use rustc_parse::exp; @@ -33,7 +33,7 @@ pub enum RawAsmArgKind { } /// Validated assembly arguments, ready for macro expansion. -pub struct AsmArgs { +struct AsmArgs { pub templates: Vec>, pub operands: Vec<(ast::InlineAsmOperand, Span)>, named_args: FxIndexMap, @@ -144,6 +144,7 @@ fn parse_asm_operand<'a>( })) } +// Public for rustfmt pub fn parse_raw_asm_args<'a>( p: &mut Parser<'a>, sp: Span, @@ -258,26 +259,17 @@ fn parse_args<'a>( tts: TokenStream, asm_macro: AsmMacro, ) -> PResult<'a, AsmArgs> { - let mut p = ecx.new_parser_from_tts(tts); - parse_asm_args(&mut p, sp, asm_macro) + let raw_args = parse_raw_asm_args(&mut ecx.new_parser_from_tts(tts), sp, asm_macro)?; + validate_raw_asm_args(ecx, asm_macro, raw_args) } -// public for use in rustfmt -// FIXME: use `RawAsmArg` in the formatting code instead. -pub fn parse_asm_args<'a>( - p: &mut Parser<'a>, - sp: Span, - asm_macro: AsmMacro, -) -> PResult<'a, AsmArgs> { - let raw_args = parse_raw_asm_args(p, sp, asm_macro)?; - validate_raw_asm_args(p.dcx(), asm_macro, raw_args) -} - -pub fn validate_raw_asm_args<'a>( - dcx: DiagCtxtHandle<'a>, +fn validate_raw_asm_args<'a>( + ecx: &ExtCtxt<'a>, asm_macro: AsmMacro, raw_args: Vec, ) -> PResult<'a, AsmArgs> { + let dcx = ecx.dcx(); + let mut args = AsmArgs { templates: vec![], operands: vec![], diff --git a/src/tools/rustfmt/src/parse/macros/asm.rs b/src/tools/rustfmt/src/parse/macros/asm.rs index 58c8d21bd7a4c..18e3386f4f10c 100644 --- a/src/tools/rustfmt/src/parse/macros/asm.rs +++ b/src/tools/rustfmt/src/parse/macros/asm.rs @@ -1,11 +1,14 @@ use rustc_ast::ast; -use rustc_builtin_macros::asm::{AsmArgs, parse_asm_args}; +use rustc_builtin_macros::asm::{RawAsmArg, parse_raw_asm_args}; use crate::rewrite::RewriteContext; #[allow(dead_code)] -pub(crate) fn parse_asm(context: &RewriteContext<'_>, mac: &ast::MacCall) -> Option { +pub(crate) fn parse_asm( + context: &RewriteContext<'_>, + mac: &ast::MacCall, +) -> Option> { let ts = mac.args.tokens.clone(); let mut parser = super::build_parser(context, ts); - parse_asm_args(&mut parser, mac.span(), ast::AsmMacro::Asm).ok() + parse_raw_asm_args(&mut parser, mac.span(), ast::AsmMacro::Asm).ok() } From 6cc60a62cab9103cc2254ca7cc66ce0106a0bf9e Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 30 Apr 2025 11:23:09 +0200 Subject: [PATCH 10/10] support `#[cfg(...)]` on arguments to the `asm!` macros --- compiler/rustc_builtin_macros/messages.ftl | 5 + compiler/rustc_builtin_macros/src/asm.rs | 87 ++++++++++++++- compiler/rustc_builtin_macros/src/errors.rs | 7 ++ compiler/rustc_feature/src/unstable.rs | 2 + compiler/rustc_span/src/symbol.rs | 1 + tests/ui/asm/cfg-parse-error.rs | 45 ++++++++ tests/ui/asm/cfg-parse-error.stderr | 24 +++++ tests/ui/asm/cfg.rs | 100 ++++++++++++++++++ .../ui/feature-gates/feature-gate-asm_cfg.rs | 48 +++++++++ .../feature-gates/feature-gate-asm_cfg.stderr | 57 ++++++++++ 10 files changed, 373 insertions(+), 3 deletions(-) create mode 100644 tests/ui/asm/cfg-parse-error.rs create mode 100644 tests/ui/asm/cfg-parse-error.stderr create mode 100644 tests/ui/asm/cfg.rs create mode 100644 tests/ui/feature-gates/feature-gate-asm_cfg.rs create mode 100644 tests/ui/feature-gates/feature-gate-asm_cfg.stderr diff --git a/compiler/rustc_builtin_macros/messages.ftl b/compiler/rustc_builtin_macros/messages.ftl index 73be954cefd76..9e0fe255e999b 100644 --- a/compiler/rustc_builtin_macros/messages.ftl +++ b/compiler/rustc_builtin_macros/messages.ftl @@ -1,6 +1,11 @@ builtin_macros_alloc_error_must_be_fn = alloc_error_handler must be a function builtin_macros_alloc_must_statics = allocators must be statics +builtin_macros_asm_attribute_not_supported = + this attribute is not supported on assembly +builtin_macros_asm_cfg = + the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable + builtin_macros_asm_clobber_abi = clobber_abi builtin_macros_asm_clobber_no_reg = asm with `clobber_abi` must specify explicit registers for outputs builtin_macros_asm_clobber_outputs = generic outputs diff --git a/compiler/rustc_builtin_macros/src/asm.rs b/compiler/rustc_builtin_macros/src/asm.rs index c8000bbe70b6f..ce791f37813be 100644 --- a/compiler/rustc_builtin_macros/src/asm.rs +++ b/compiler/rustc_builtin_macros/src/asm.rs @@ -10,18 +10,20 @@ use rustc_index::bit_set::GrowableBitSet; use rustc_parse::exp; use rustc_parse::parser::{ExpKeywordPair, Parser}; use rustc_session::lint; -use rustc_span::{ErrorGuaranteed, InnerSpan, Span, Symbol, kw}; +use rustc_session::parse::feature_err; +use rustc_span::{ErrorGuaranteed, InnerSpan, Span, Symbol, kw, sym}; use rustc_target::asm::InlineAsmArch; use smallvec::smallvec; use {rustc_ast as ast, rustc_parse_format as parse}; -use crate::errors; use crate::util::{ExprToSpannedString, expr_to_spanned_string}; +use crate::{errors, fluent_generated as fluent}; /// An argument to one of the `asm!` macros. The argument is syntactically valid, but is otherwise /// not validated at all. pub struct RawAsmArg { pub kind: RawAsmArgKind, + pub attributes: AsmAttrVec, pub span: Span, } @@ -43,6 +45,48 @@ struct AsmArgs { pub options_spans: Vec, } +/// 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 mut attributes = ast::AttrVec::new(); + loop { + if p.token != token::Pound { + break; + } + + let attr = p.parse_attribute(rustc_parse::parser::attr::InnerAttrPolicy::Permitted)?; + attributes.push(attr); + } + + Ok(Self(attributes)) + } +} +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> { + 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!`) /// @@ -158,10 +202,12 @@ pub fn parse_raw_asm_args<'a>( let mut args = Vec::new(); + let attributes = AsmAttrVec::parse(p)?; let first_template = p.parse_expr()?; args.push(RawAsmArg { span: first_template.span, kind: RawAsmArgKind::Template(first_template), + attributes, }); let mut allow_templates = true; @@ -180,6 +226,7 @@ pub fn parse_raw_asm_args<'a>( break; } // accept trailing commas + let attributes = AsmAttrVec::parse(p)?; let span_start = p.token.span; // Parse clobber_abi @@ -189,6 +236,7 @@ pub fn parse_raw_asm_args<'a>( args.push(RawAsmArg { kind: RawAsmArgKind::ClobberAbi(parse_clobber_abi(p)?), span: span_start.to(p.prev_token.span), + attributes, }); continue; @@ -201,6 +249,7 @@ pub fn parse_raw_asm_args<'a>( args.push(RawAsmArg { kind: RawAsmArgKind::Options(parse_options(p, asm_macro)?), span: span_start.to(p.prev_token.span), + attributes, }); continue; @@ -223,6 +272,7 @@ pub fn parse_raw_asm_args<'a>( args.push(RawAsmArg { span: span_start.to(p.prev_token.span), kind: RawAsmArgKind::Operand(name, op), + attributes, }); } else if allow_templates { let template = p.parse_expr()?; @@ -244,7 +294,11 @@ pub fn parse_raw_asm_args<'a>( } } - args.push(RawAsmArg { span: template.span, kind: RawAsmArgKind::Template(template) }); + args.push(RawAsmArg { + span: template.span, + kind: RawAsmArgKind::Template(template), + attributes, + }); } else { p.unexpected_any()? } @@ -270,6 +324,13 @@ fn validate_raw_asm_args<'a>( ) -> PResult<'a, AsmArgs> { let dcx = ecx.dcx(); + let strip_unconfigured = rustc_expand::config::StripUnconfigured { + sess: ecx.sess, + features: Some(ecx.ecfg.features), + config_tokens: false, + lint_node_id: ecx.current_expansion.lint_node_id, + }; + let mut args = AsmArgs { templates: vec![], operands: vec![], @@ -283,6 +344,26 @@ fn validate_raw_asm_args<'a>( let mut allow_templates = true; for arg in raw_args { + for attr in arg.attributes.0.iter() { + match attr.name() { + Some(sym::cfg | sym::cfg_attr) => { + if !ecx.ecfg.features.asm_cfg() { + let span = attr.span(); + feature_err(ecx.sess, sym::asm_cfg, span, fluent::builtin_macros_asm_cfg) + .emit(); + } + } + _ => { + ecx.dcx().emit_err(errors::AsmAttributeNotSupported { span: attr.span() }); + } + } + } + + // Skip arguments that are configured out. + if ecx.ecfg.features.asm_cfg() && strip_unconfigured.configure(arg.attributes).is_none() { + continue; + } + match arg.kind { RawAsmArgKind::Template(template) => { // The error for the first template is delayed. diff --git a/compiler/rustc_builtin_macros/src/errors.rs b/compiler/rustc_builtin_macros/src/errors.rs index d14ad8f40144c..012815db318d1 100644 --- a/compiler/rustc_builtin_macros/src/errors.rs +++ b/compiler/rustc_builtin_macros/src/errors.rs @@ -795,6 +795,13 @@ pub(crate) struct AsmRequiresTemplate { pub(crate) span: Span, } +#[derive(Diagnostic)] +#[diag(builtin_macros_asm_attribute_not_supported)] +pub(crate) struct AsmAttributeNotSupported { + #[primary_span] + pub(crate) span: Span, +} + #[derive(Diagnostic)] #[diag(builtin_macros_asm_expected_comma)] pub(crate) struct AsmExpectedComma { diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index cbc121e3632a6..9c0a6733d5294 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -369,6 +369,8 @@ declare_features! ( (unstable, arbitrary_self_types, "1.23.0", Some(44874)), /// Allows inherent and trait methods with arbitrary self types that are raw pointers. (unstable, arbitrary_self_types_pointers, "1.83.0", Some(44874)), + /// Allows #[cfg(...)] on inline assembly templates and operands. + (unstable, asm_cfg, "CURRENT_RUSTC_VERSION", Some(140364)), /// Enables experimental inline assembly support for additional architectures. (unstable, asm_experimental_arch, "1.58.0", Some(93335)), /// Enables experimental register support in inline assembly. diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 32a5aff0cb327..9740e0ce09096 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -472,6 +472,7 @@ symbols! { as_ref, as_str, asm, + asm_cfg, asm_const, asm_experimental_arch, asm_experimental_reg, diff --git a/tests/ui/asm/cfg-parse-error.rs b/tests/ui/asm/cfg-parse-error.rs new file mode 100644 index 0000000000000..1ebc0529f4a42 --- /dev/null +++ b/tests/ui/asm/cfg-parse-error.rs @@ -0,0 +1,45 @@ +//@ needs-asm-support +#![feature(asm_cfg)] + +use std::arch::asm; + +fn main() { + unsafe { + // Templates are not allowed after operands (even if the operands are configured out). + asm!( + "", + #[cfg(false)] + clobber_abi("C"), + #[cfg(false)] + options(att_syntax), + #[cfg(false)] + a = out(reg) x, + "", + //~^ ERROR expected one of `clobber_abi`, `const` + ); + asm!( + #[cfg(false)] + "", + #[cfg(false)] + const { + 5 + }, + "", //~ ERROR expected one of `clobber_abi`, `const` + ); + + // This is currently accepted because `const { 5 }` parses as an expression. + asm!( + #[cfg(false)] + const { + 5 + }, + "", + ); + // This is not accepted because `a = out(reg) x` is not a valid expresion. + asm!( + #[cfg(false)] + a = out(reg) x, //~ ERROR expected token: `,` + "", + ); + } +} diff --git a/tests/ui/asm/cfg-parse-error.stderr b/tests/ui/asm/cfg-parse-error.stderr new file mode 100644 index 0000000000000..9023cd2a57ed3 --- /dev/null +++ b/tests/ui/asm/cfg-parse-error.stderr @@ -0,0 +1,24 @@ +error: expected one of `clobber_abi`, `const`, `in`, `inlateout`, `inout`, `label`, `lateout`, `options`, `out`, or `sym`, found `""` + --> $DIR/cfg-parse-error.rs:17:13 + | +LL | a = out(reg) x, + | - expected one of 10 possible tokens +LL | "", + | ^^ unexpected token + +error: expected one of `clobber_abi`, `const`, `in`, `inlateout`, `inout`, `label`, `lateout`, `options`, `out`, or `sym`, found `""` + --> $DIR/cfg-parse-error.rs:27:13 + | +LL | }, + | - expected one of 10 possible tokens +LL | "", + | ^^ unexpected token + +error: expected token: `,` + --> $DIR/cfg-parse-error.rs:41:26 + | +LL | a = out(reg) x, + | ^ expected `,` + +error: aborting due to 3 previous errors + diff --git a/tests/ui/asm/cfg.rs b/tests/ui/asm/cfg.rs new file mode 100644 index 0000000000000..a8b72aee7000f --- /dev/null +++ b/tests/ui/asm/cfg.rs @@ -0,0 +1,100 @@ +// Check that `cfg` and `cfg_attr` work as expected. +// +//@ revisions: reva revb +//@ only-x86_64 +//@ run-pass +#![feature(asm_cfg, cfg_match)] + +use std::arch::{asm, naked_asm}; + +#[unsafe(naked)] +extern "C" fn ignore_const_operand() -> u64 { + naked_asm!( + "mov rax, 5", + #[cfg(revb)] + "mov rax, {a}", + "ret", + #[cfg(revb)] + a = const 10, + ) +} + +#[unsafe(naked)] +extern "C" fn ignore_const_operand_cfg_attr() -> u64 { + naked_asm!( + "mov rax, 5", + #[cfg_attr(true, cfg(revb))] + "mov rax, {a}", + "ret", + #[cfg_attr(true, cfg(revb))] + a = const 10, + ) +} + +#[unsafe(naked)] +extern "C" fn const_operand() -> u64 { + naked_asm!( + "mov rax, {a}", + "ret", + #[cfg(reva)] + a = const 5, + #[cfg(revb)] + a = const 10, + ) +} + +fn options() { + // Without the cfg, this throws an error that the `noreturn` option is provided twice. + unsafe { + asm!( + "nop", + #[cfg(false)] + options(att_syntax), + options(att_syntax) + ) + } +} + +fn clobber_abi() { + // Without the cfg, this throws an error that the "C" abi is provided twice. + unsafe { + asm!( + "nop", + #[cfg(false)] + clobber_abi("C"), + clobber_abi("C"), + ); + } +} + +#[unsafe(naked)] +extern "C" fn first_template() -> u64 { + naked_asm!( + #[cfg(reva)] + "mov rax, 5", + #[cfg(revb)] + "mov rax, 10", + "ret", + ) +} + +pub fn main() { + std::cfg_match! { + reva => { + assert_eq!(const_operand(), 5); + assert_eq!(ignore_const_operand_cfg_attr(), 5); + assert_eq!(ignore_const_operand(), 5); + assert_eq!(first_template(), 5); + + } + revb => { + assert_eq!(const_operand(), 10); + assert_eq!(ignore_const_operand_cfg_attr(), 10); + assert_eq!(ignore_const_operand(), 10); + assert_eq!(first_template(), 10); + + } + } + options(); + clobber_abi(); +} diff --git a/tests/ui/feature-gates/feature-gate-asm_cfg.rs b/tests/ui/feature-gates/feature-gate-asm_cfg.rs new file mode 100644 index 0000000000000..ef8bf75b6929d --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-asm_cfg.rs @@ -0,0 +1,48 @@ +//@ only-x86_64 +#![crate_type = "lib"] + +use std::arch::{asm, global_asm, naked_asm}; + +global_asm!( + "nop", + #[cfg(false)] + //~^ ERROR the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable + "nop" +); + +#[unsafe(naked)] +#[no_mangle] +extern "C" fn naked() { + naked_asm!( + "mov rax, 5", + #[cfg(false)] + //~^ ERROR the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable + "mov rax, {a}", + "ret", + #[cfg(false)] + //~^ ERROR the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable + a = const 10, + ) +} + +fn asm() { + unsafe { + asm!( + "nop", + #[cfg(false)] + //~^ ERROR the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable + clobber_abi("C"), + clobber_abi("C"), //~ ERROR `C` ABI specified multiple times + ); + } +} + +fn bad_attribute() { + unsafe { + asm!( + #[inline] + //~^ ERROR this attribute is not supported on assembly + "nop" + ) + }; +} diff --git a/tests/ui/feature-gates/feature-gate-asm_cfg.stderr b/tests/ui/feature-gates/feature-gate-asm_cfg.stderr new file mode 100644 index 0000000000000..e92d1e8c48747 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-asm_cfg.stderr @@ -0,0 +1,57 @@ +error[E0658]: the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable + --> $DIR/feature-gate-asm_cfg.rs:8:5 + | +LL | #[cfg(false)] + | ^^^^^^^^^^^^^ + | + = note: see issue #140364 for more information + = help: add `#![feature(asm_cfg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable + --> $DIR/feature-gate-asm_cfg.rs:18:9 + | +LL | #[cfg(false)] + | ^^^^^^^^^^^^^ + | + = note: see issue #140364 for more information + = help: add `#![feature(asm_cfg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable + --> $DIR/feature-gate-asm_cfg.rs:22:9 + | +LL | #[cfg(false)] + | ^^^^^^^^^^^^^ + | + = note: see issue #140364 for more information + = help: add `#![feature(asm_cfg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error[E0658]: the `#[cfg(/* ... */)]` and `#[cfg_attr(/* ... */)]` attributes on assembly are unstable + --> $DIR/feature-gate-asm_cfg.rs:32:13 + | +LL | #[cfg(false)] + | ^^^^^^^^^^^^^ + | + = note: see issue #140364 for more information + = help: add `#![feature(asm_cfg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: this attribute is not supported on assembly + --> $DIR/feature-gate-asm_cfg.rs:43:13 + | +LL | #[inline] + | ^^^^^^^^^ + +error: `C` ABI specified multiple times + --> $DIR/feature-gate-asm_cfg.rs:35:13 + | +LL | clobber_abi("C"), + | ---------------- previously specified here +LL | clobber_abi("C"), + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 6 previous errors + +For more information about this error, try `rustc --explain E0658`.