Repository: rust-lang/rust
Pull request: Reorder inline asm operands in pretty printer to satisfy grammar constraints

After macro expansion, named `asm!` operands are converted to positional operands and the template string uses numeric indices. However, the pretty printer previously emitted operands in their original AST order, which could place positional (formerly named) register-class operands after explicit register operands. This violates the `asm!` grammar rule that positional arguments cannot follow explicit register arguments, causing the expanded output from `rustc -Zunpretty=expanded` to fail to reparse.

When reordering is needed, the fix partitions operands into non-explicit and explicit register groups, emits non-explicit operands first, then explicit register operands, and remaps template placeholder indices (`{N}`) to match the new positions. When operands are already correctly ordered, the original code path is used unchanged.

## Example

**before** (`rustc 1.96.0-nightly (3b1b0ef4d 2026-03-11)`):

```rust
#![feature(prelude_import)]
#![no_std]
extern crate std;
#[prelude_import]
use ::std::prelude::rust_2015::*;
//@ pretty-mode:expanded
//@ pp-exact:asm-operand-order.pp
//@ only-x86_64

use std::arch::asm;

pub fn main() {
    unsafe {
        asm!("{1}", out("rax") _, in(reg) 4);
        asm!("{1}", out("rax") _, in(reg) 4, options(nostack));
        asm!("{1} {2}", out("rax") _, in(reg) 4, in(reg) 5);
    }
}
```

**after** (this branch):

```rust
#![feature(prelude_import)]
#![no_std]
extern crate std;
#[prelude_import]
use ::std::prelude::rust_2015::*;
//@ pretty-mode:expanded
//@ pp-exact:asm-operand-order.pp
//@ only-x86_64

use std::arch::asm;

pub fn main() {
    unsafe {
        asm!("{0}", in(reg) 4, out("rax") _);
        asm!("{0}", in(reg) 4, out("rax") _, options(nostack));
        asm!("{0} {1}", in(reg) 4, in(reg) 5, out("rax") _);
    }
}
```
Notice the operand reordering: in the before, explicit register operands (`out("rax")`) appear before positional operands (`in(reg)`), violating the grammar (E0662). The template references are also wrong (`{1}` instead of `{0}`). The after moves positional operands first and renumbers the template references accordingly.

<!-- homu-ignore:start -->
<!--
If this PR is related to an unstable feature or an otherwise tracked effort,
please link to the relevant tracking issue here. If you don't know of a related
tracking issue or there are none, feel free to ignore this.

This PR will get automatically assigned to a reviewer. In case you would like
a specific user to review your work, you can assign it to them by using

    r? <reviewer name>
-->
<!-- homu-ignore:end -->


Changed files:
filename: compiler/rustc_ast_pretty/src/pprust/state.rs
patch: @@ -1649,6 +1649,85 @@ impl<'a> State<'a> {
         );
     }
 
+    fn inline_asm_template_and_operands<'asm>(
+        asm: &'asm ast::InlineAsm,
+    ) -> (String, Vec<&'asm InlineAsmOperand>) {
+        fn is_explicit_reg(op: &InlineAsmOperand) -> bool {
+            match op {
+                InlineAsmOperand::In { reg, .. }
+                | InlineAsmOperand::Out { reg, .. }
+                | InlineAsmOperand::InOut { reg, .. }
+                | InlineAsmOperand::SplitInOut { reg, .. } => {
+                    matches!(reg, InlineAsmRegOrRegClass::Reg(_))
+                }
+                InlineAsmOperand::Const { .. }
+                | InlineAsmOperand::Sym { .. }
+                | InlineAsmOperand::Label { .. } => false,
+            }
+        }
+
+        // After macro expansion, named operands become positional. The grammar
+        // requires positional operands to precede explicit register operands,
+        // so we must reorder when any non-explicit operand follows an explicit
+        // one. When no reordering is needed, we use the original template
+        // string and operand order to avoid duplicating the Display logic in
+        // InlineAsmTemplatePiece.
+        let needs_reorder = {
+            let mut seen_explicit = false;
+            asm.operands.iter().any(|(op, _)| {
+                if is_explicit_reg(op) {
+                    seen_explicit = true;
+                    false
+                } else {
+                    seen_explicit
+                }
+            })
+        };
+
+        if !needs_reorder {
+            let template = InlineAsmTemplatePiece::to_string(&asm.template);
+            let operands = asm.operands.iter().map(|(op, _)| op).collect();
+            return (template, operands);
+        }
+
+        let mut non_explicit = Vec::new();
+        let mut explicit = Vec::new();
+        for (i, (op, _)) in asm.operands.iter().enumerate() {
+            if is_explicit_reg(op) {
+                explicit.push(i);
+            } else {
+                non_explicit.push(i);
+            }
+        }
+        let order = non_explicit.into_iter().chain(explicit).collect::<Vec<_>>();
+
+        // Build old-index -> new-index mapping for template renumbering.
+        let mut old_to_new = vec![0usize; asm.operands.len()];
+        for (new_idx, old_idx) in order.iter().copied().enumerate() {
+            old_to_new[old_idx] = new_idx;
+        }
+
+        // Remap template placeholder indices and reuse the existing Display
+        // impl to build the template string.
+        let remapped = asm
+            .template
+            .iter()
+            .map(|piece| match piece {
+                InlineAsmTemplatePiece::Placeholder { operand_idx, modifier, span } => {
+                    InlineAsmTemplatePiece::Placeholder {
+                        operand_idx: old_to_new[*operand_idx],
+                        modifier: *modifier,
+                        span: *span,
+                    }
+                }
+                other => other.clone(),
+            })
+            .collect::<Vec<_>>();
+        let template = InlineAsmTemplatePiece::to_string(&remapped);
+        let operands = order.iter().map(|&idx| &asm.operands[idx].0).collect();
+        (template, operands)
+    }
+
     fn print_inline_asm(&mut self, asm: &ast::InlineAsm) {
         enum AsmArg<'a> {
             Template(String),
@@ -1657,8 +1736,9 @@ impl<'a> State<'a> {
             Options(InlineAsmOptions),
         }
 
-        let mut args = vec![AsmArg::Template(InlineAsmTemplatePiece::to_string(&asm.template))];
-        args.extend(asm.operands.iter().map(|(o, _)| AsmArg::Operand(o)));
+        let (template, operands) = Self::inline_asm_template_and_operands(asm);
+        let mut args = vec![AsmArg::Template(template)];
+        args.extend(operands.into_iter().map(AsmArg::Operand));
         for (abi, _) in &asm.clobber_abis {
             args.push(AsmArg::ClobberAbi(*abi));
         }

filename: tests/pretty/asm-operand-order.pp
patch: @@ -0,0 +1,19 @@
+#![feature(prelude_import)]
+#![no_std]
+extern crate std;
+#[prelude_import]
+use ::std::prelude::rust_2015::*;
+//@ pretty-mode:expanded
+//@ pp-exact:asm-operand-order.pp
+//@ only-x86_64
+
+use std::arch::asm;
+
+pub fn main() {
+    unsafe {
+        asm!("{0}", in(reg) 4, out("rax") _);
+        asm!("{0}", in(reg) 4, out("rax") _, options(nostack));
+        asm!("{0} {1}", in(reg) 4, in(reg) 5, out("rax") _);
+        asm!("{0}", const 5, out("rax") _);
+    }
+}

filename: tests/pretty/asm-operand-order.rs
patch: @@ -0,0 +1,14 @@
+//@ pretty-mode:expanded
+//@ pp-exact:asm-operand-order.pp
+//@ only-x86_64
+
+use std::arch::asm;
+
+pub fn main() {
+    unsafe {
+        asm!("{val}", out("rax") _, val = in(reg) 4);
+        asm!("{val}", out("rax") _, val = in(reg) 4, options(nostack));
+        asm!("{a} {b}", out("rax") _, a = in(reg) 4, b = in(reg) 5);
+        asm!("{val}", out("rax") _, val = const 5);
+    }
+}