Skip to content

add asm_cfg: #[cfg(...)] within asm! #140367

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

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

folkertdev
Copy link
Contributor

@folkertdev folkertdev commented Apr 27, 2025

tracking issue: #140364
blocked on: #140490

This feature was discussed in #140279. It allows configuring templates and operands in the assembly macros, for example:

asm!( // or global_asm! or naked_asm!
    "nop",
    #[cfg(target_feature = "sse2")]
    "nop",
    // ...
    #[cfg(target_feature = "sse2")]
    a = const 123, // only used on sse2
);

r? @tgross35

cc @traviscross @Amanieu

Now builds on #140490, which should be merged first.

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Apr 27, 2025
let is_configured_out =
ecx.ecfg.features.asm_cfg() && strip_unconfigured.configure(attributes).is_none();

let template = p.parse_expr()?;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The fact that we use parse_expr here mean that this does not work, and does not produce a great error message. This complains about an unexpected , after the 6, but the real problem is that this cannot possibly be or expand to a string literal.

asm!(
    #[cfg(false)]
    a = const 6,
    "nop",
);

Maybe we should specialize the parser to specifically only parse string literals and item macros?

if p.token == token::Eof {
return Err(dcx.create_err(errors::AsmRequiresTemplate { span: sp }));
}

let first_template = p.parse_expr()?;
let first_template = loop {
let attributes = AsmAttrVec::parse(ecx, p)?;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

attributes are now parsed before the parse_expr below, and never attached to the expression. In theory that could interact with #[feature(stmt_expr_attributes)], but:

  • that feature is unstable
  • any attributes it parsed would have no effect

Comment on lines 90 to 92
_ => {
ecx.dcx().emit_err(errors::AsmAttributeNotSupported { span: attr.span() });
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

non-cfg attributes are now accepted by the parser, but emit this error. Previously attributes were only parsed (and later rejected, unless #[feature(stmt_expr_attributes)] was enabled) on expressions. We now also always parse them on operands.

@rust-log-analyzer

This comment has been minimized.

@folkertdev
Copy link
Contributor Author

Hmm, rust analyzer also parses the assembly, but from what I can see it has no way of evaluating the cfgs while parsing. Maybe that can change, but if not we'll have to fundamentally change the parser to just parse, and then later have a validation pass. I tried that before and it's possible but messy, and you need to store a lot more information in AsmArgs (that rust-analyzer then would also have to process).

@rust-lang/rust-analyzer how can we make this work?

@ChayimFriedman2
Copy link
Contributor

(I haven't looked at the PR, but) parsing does not evaluated cfgs. Hir lowering does.

@folkertdev
Copy link
Contributor Author

Well in any case the parser doesn't. I'm not exactly sure where to draw the line for when HIR lowering starts, but certainly macro expansion of builtin macros is able to do it, e.g. here for cfg!:

let matches_cfg = attr::cfg_matches(
&cfg,
&cx.sess,
cx.current_expansion.lint_node_id,
Some(cx.ecfg.features),
);
MacEager::expr(cx.expr_bool(sp, matches_cfg))

Anyway, are you saying that rust-analyzer just cannot evaluate cfg's while parsing the asm! macro? We can delay it, but I think that just means that rust-analyzer would have to duplicate the logic.

@Veykril
Copy link
Member

Veykril commented Apr 28, 2025

rust-analyzer has to duplicate these changes either way. We don't use any of the touched rustc crates here.

@folkertdev
Copy link
Contributor Author

I got confused between rust-analyzer and rustfmt here, sorry about that.

I've also opened #140490, which will make adding the cfg support a lot easier ( have that working on a branch). So for now:

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 30, 2025
@rustbot
Copy link
Collaborator

rustbot commented Apr 30, 2025

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@tgross35
Copy link
Contributor

tgross35 commented May 2, 2025

I'm still take a look through this, but I don't know enough about this area of the compiler to give a good review. Picking somebody who shows up in the blame a lot:

r? @nnethercote

@rustbot
Copy link
Collaborator

rustbot commented May 2, 2025

Some changes occurred in src/tools/rustfmt

cc @rust-lang/rustfmt

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

Copy link
Contributor

@traviscross traviscross left a comment

Choose a reason for hiding this comment

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

@rustbot author

Comment on lines +55 to +58
loop {
if p.token != token::Pound {
break;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be phrased as while, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, but I think this whole loop/function needs to change, to make the parsing of attributes consistent.

@nnethercote you seem to have worked on the attribute parsing a bit (and are the current reviewer here), what would be a good way to do this? e.g. this function is private, but I think it's what should be used here:

pub(super) fn parse_outer_attributes(&mut self) -> PResult<'a, AttrWrapper> {

It returns an AttrWrapper which deliberately does not give access to the inner attributes until you call collect_tokens (another private function). Does it make sense to make those functions public, or should the asm attribute parsing live in rustc_parse instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

after thinking about this some more, it might makes sense to, now that parsing and validation are split, to move the asm parsing code into rustc_parse?


fn main() {
unsafe {
// Templates are not allowed after operands (even if the operands are configured out).
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a reasonable restriction? I'm thinking here about macros that might want to expand something like:

cfg_match! { 
    one => { "jmp here", options(some_option), };
    two => { "jmp there", options(other_option), };
}

Into:

#[cfg(one)]
"jmp here",
#[cfg(one)]
options(some_option),
#[cfg(and(not(one), two)]
"jmp there",
#[cfg(and(not(one), two)]
options(other_option),

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm yeah that's a good point.

We'd have to make some further parser changes to make that work. Currently the parsing rules change based on whether a template has already been parsed (mostly for error message quality).

A second issue is that templates are currently parsed with the general expression parser, but some of the non-template arguments parse as valid expressions (e.g. options(noreturn) looks like a function call, and const { 5 } is also a valid expression).

A solution is to instead parse specifically string literals and macro calls, those are templates, and if those fail to parse, try to parse an operand.

//@ revisions: reva revb
//@ only-x86_64
//@ run-pass
#![feature(asm_cfg, cfg_match)]
Copy link
Contributor

Choose a reason for hiding this comment

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

It'd be worth showing that cfg_match! works within asm!.

Copy link
Contributor Author

@folkertdev folkertdev May 3, 2025

Choose a reason for hiding this comment

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

It really doesn't work right now. I believe there are some issues with using cfg_match! in expression position.

error: macro expansion ignores `#` and any tokens following
  --> /rustc/FAKE_PREFIX/library/core/src/macros/mod.rs:255:13
   |
  ::: /home/folkertdev/rust/rust/tests/ui/asm/cfg.rs:84:9
   |
LL | /         cfg_match! {
LL | |         reva => { "mov rax, 5" }
LL | |         revb => { "mov rax, 10" }
LL | |         },
   | |_________- caused by the macro expansion here
   |
   = note: the usage of `cfg_match!` is likely invalid in expression context
help: you might be missing a semicolon here
  --> /home/folkertdev/rust/rust/tests/ui/asm/cfg.rs:87:10
   |
LL |         };,
   |          +

Also using

cfg_match! {
    reva => "mov rax, 5",
    revb => "mov rax, 10",
},

Makes macro expansion hit the recursion limit

Copy link
Contributor

@traviscross traviscross May 3, 2025

Choose a reason for hiding this comment

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

Right now you have to write cfg_match! {{ .. }} for it to work in expression position (no longer requiring the double brackets is a stabilization blocker for cfg_match!).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm still struggling with getting this to work. I think this should work, but it expands to an Expr::Block, which is not a string, and hence rejected.

#[unsafe(naked)]
extern "C" fn with_cfg_match() -> u64 {
    core::arch::naked_asm!(
        cfg_match!{{
            unix => {"mov rax, 5"}
            _ => {"mov rax, 10"}
        }},
        "ret",
    )
}

using this instead

        cfg_match!{{
            unix => "mov rax, 5",
            _ => "mov rax, 10",
        }},

does not parse,

error: expected identifier, found reserved identifier `_`
  --> src/main.rs:10:9
   |
10 | /         cfg_match!{{
11 | |             unix => "mov rax, 5",
12 | |             _ => "mov rax, 10",
13 | |         }},
   | |__________^ expected identifier, found reserved identifier

So, am I still using this macro wrong?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants