about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/tools/rust-analyzer/.typos.toml1
-rw-r--r--src/tools/rust-analyzer/Cargo.lock1
-rw-r--r--src/tools/rust-analyzer/Cargo.toml1
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/body.rs36
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs14
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/body/lower/asm.rs254
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/hir.rs131
-rw-r--r--src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs6
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs34
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs21
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs67
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs26
-rw-r--r--src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs99
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/has_source.rs27
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/lib.rs22
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/semantics.rs70
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs21
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs22
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/item.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/defs.rs37
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/lib.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/rename.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/search.rs47
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/doc_links.rs8
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/moniker.rs7
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/navigation_target.rs29
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/rename.rs56
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/format.rs24
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_asm.html97
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html14
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs63
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs179
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/parser.rs8
-rw-r--r--src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs79
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs2
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/asm_expr.rast83
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/asm_expr.rs10
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/builtin_expr.rast2
-rw-r--r--src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/builtin_expr.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs3
-rw-r--r--src/tools/rust-analyzer/crates/syntax/rust.ungram27
-rw-r--r--src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs501
-rw-r--r--src/tools/rust-analyzer/crates/test-utils/src/minicore.rs23
-rw-r--r--src/tools/rust-analyzer/xtask/Cargo.toml1
-rw-r--r--src/tools/rust-analyzer/xtask/src/codegen/grammar.rs40
-rw-r--r--src/tools/rust-analyzer/xtask/src/codegen/grammar/ast_src.rs26
49 files changed, 2070 insertions, 164 deletions
diff --git a/src/tools/rust-analyzer/.typos.toml b/src/tools/rust-analyzer/.typos.toml
index e7e764ce035..febfb233bd9 100644
--- a/src/tools/rust-analyzer/.typos.toml
+++ b/src/tools/rust-analyzer/.typos.toml
@@ -15,6 +15,7 @@ extend-ignore-re = [
     '"flate2"',
     "raison d'ĂȘtre",
     "inout",
+    "INOUT",
     "optin"
 ]
 
diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock
index 6eaded6da14..85ef3a6ba92 100644
--- a/src/tools/rust-analyzer/Cargo.lock
+++ b/src/tools/rust-analyzer/Cargo.lock
@@ -2624,6 +2624,7 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "directories",
+ "either",
  "flate2",
  "itertools",
  "proc-macro2",
diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml
index e55628c8dcf..9e972e12118 100644
--- a/src/tools/rust-analyzer/Cargo.toml
+++ b/src/tools/rust-analyzer/Cargo.toml
@@ -185,6 +185,7 @@ style = { level = "warn", priority = -1 }
 suspicious = { level = "warn", priority = -1 }
 
 ## allow following lints
+too_long_first_doc_paragraph = "allow"
 # subjective
 single_match = "allow"
 # () makes a fine error in most cases
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body.rs b/src/tools/rust-analyzer/crates/hir-def/src/body.rs
index f5fe8f87701..34db43939a7 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/body.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/body.rs
@@ -100,7 +100,14 @@ pub struct BodySourceMap {
     field_map_back: FxHashMap<ExprId, FieldSource>,
     pat_field_map_back: FxHashMap<PatId, PatFieldSource>,
 
-    format_args_template_map: FxHashMap<ExprId, Vec<(syntax::TextRange, Name)>>,
+    template_map: Option<
+        Box<(
+            // format_args!
+            FxHashMap<ExprId, Vec<(syntax::TextRange, Name)>>,
+            // asm!
+            FxHashMap<ExprId, Vec<(syntax::TextRange, usize)>>,
+        )>,
+    >,
 
     expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, MacroFileId>,
 
@@ -426,7 +433,16 @@ impl BodySourceMap {
         node: InFile<&ast::FormatArgsExpr>,
     ) -> Option<&[(syntax::TextRange, Name)]> {
         let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::Expr>);
-        self.format_args_template_map.get(self.expr_map.get(&src)?).map(std::ops::Deref::deref)
+        self.template_map.as_ref()?.0.get(self.expr_map.get(&src)?).map(std::ops::Deref::deref)
+    }
+
+    pub fn asm_template_args(
+        &self,
+        node: InFile<&ast::AsmExpr>,
+    ) -> Option<(ExprId, &[(syntax::TextRange, usize)])> {
+        let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::Expr>);
+        let expr = self.expr_map.get(&src)?;
+        Some(*expr).zip(self.template_map.as_ref()?.1.get(expr).map(std::ops::Deref::deref))
     }
 
     /// Get a reference to the body source map's diagnostics.
@@ -446,11 +462,14 @@ impl BodySourceMap {
             field_map_back,
             pat_field_map_back,
             expansions,
-            format_args_template_map,
+            template_map,
             diagnostics,
             binding_definitions,
         } = self;
-        format_args_template_map.shrink_to_fit();
+        if let Some(template_map) = template_map {
+            template_map.0.shrink_to_fit();
+            template_map.1.shrink_to_fit();
+        }
         expr_map.shrink_to_fit();
         expr_map_back.shrink_to_fit();
         pat_map.shrink_to_fit();
@@ -463,4 +482,13 @@ impl BodySourceMap {
         diagnostics.shrink_to_fit();
         binding_definitions.shrink_to_fit();
     }
+
+    pub fn template_map(
+        &self,
+    ) -> Option<&(
+        FxHashMap<Idx<Expr>, Vec<(tt::TextRange, Name)>>,
+        FxHashMap<Idx<Expr>, Vec<(tt::TextRange, usize)>>,
+    )> {
+        self.template_map.as_deref()
+    }
 }
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs
index 145afc1b610..9c547574ecb 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs
@@ -1,6 +1,8 @@
 //! Transforms `ast::Expr` into an equivalent `hir_def::expr::Expr`
 //! representation.
 
+mod asm;
+
 use std::mem;
 
 use base_db::CrateId;
@@ -35,8 +37,8 @@ use crate::{
             FormatPlaceholder, FormatSign, FormatTrait,
         },
         Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, ClosureKind,
-        Expr, ExprId, InlineAsm, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability,
-        OffsetOf, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
+        Expr, ExprId, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability, OffsetOf, Pat,
+        PatId, RecordFieldPat, RecordLitField, Statement,
     },
     item_scope::BuiltinShadowMode,
     lang_item::LangItem,
@@ -693,10 +695,7 @@ impl ExprCollector<'_> {
                 }
             }
             ast::Expr::UnderscoreExpr(_) => self.alloc_expr(Expr::Underscore, syntax_ptr),
-            ast::Expr::AsmExpr(e) => {
-                let e = self.collect_expr_opt(e.expr());
-                self.alloc_expr(Expr::InlineAsm(InlineAsm { e }), syntax_ptr)
-            }
+            ast::Expr::AsmExpr(e) => self.lower_inline_asm(e, syntax_ptr),
             ast::Expr::OffsetOfExpr(e) => {
                 let container = Interned::new(TypeRef::from_ast_opt(&self.ctx(), e.ty()));
                 let fields = e.fields().map(|it| it.as_name()).collect();
@@ -1848,7 +1847,7 @@ impl ExprCollector<'_> {
             },
             syntax_ptr,
         );
-        self.source_map.format_args_template_map.insert(idx, mappings);
+        self.source_map.template_map.get_or_insert_with(Default::default).0.insert(idx, mappings);
         idx
     }
 
@@ -2061,6 +2060,7 @@ impl ExprCollector<'_> {
             is_assignee_expr: false,
         })
     }
+
     // endregion: format
 
     fn lang_path(&self, lang: LangItem) -> Option<Path> {
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/lower/asm.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/lower/asm.rs
new file mode 100644
index 00000000000..bf6ba41482c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/hir-def/src/body/lower/asm.rs
@@ -0,0 +1,254 @@
+//! Lowering of inline assembly.
+use hir_expand::name::Name;
+use intern::Symbol;
+use rustc_hash::{FxHashMap, FxHashSet};
+use syntax::{
+    ast::{self, HasName, IsString},
+    AstNode, AstPtr, AstToken, T,
+};
+use tt::{TextRange, TextSize};
+
+use crate::{
+    body::lower::{ExprCollector, FxIndexSet},
+    hir::{AsmOperand, AsmOptions, Expr, ExprId, InlineAsm, InlineAsmRegOrRegClass},
+};
+
+impl ExprCollector<'_> {
+    pub(super) fn lower_inline_asm(
+        &mut self,
+        asm: ast::AsmExpr,
+        syntax_ptr: AstPtr<ast::Expr>,
+    ) -> ExprId {
+        let mut clobber_abis = FxIndexSet::default();
+        let mut operands = vec![];
+        let mut options = AsmOptions::empty();
+
+        let mut named_pos: FxHashMap<usize, Symbol> = Default::default();
+        let mut named_args: FxHashMap<Symbol, usize> = Default::default();
+        let mut reg_args: FxHashSet<usize> = Default::default();
+        for piece in asm.asm_pieces() {
+            let slot = operands.len();
+            let mut lower_reg = |reg: Option<ast::AsmRegSpec>| {
+                let reg = reg?;
+                if let Some(string) = reg.string_token() {
+                    reg_args.insert(slot);
+                    Some(InlineAsmRegOrRegClass::Reg(Symbol::intern(string.text())))
+                } else {
+                    reg.name_ref().map(|name_ref| {
+                        InlineAsmRegOrRegClass::RegClass(Symbol::intern(&name_ref.text()))
+                    })
+                }
+            };
+
+            let op = match piece {
+                ast::AsmPiece::AsmClobberAbi(clobber_abi) => {
+                    if let Some(abi_name) = clobber_abi.string_token() {
+                        clobber_abis.insert(Symbol::intern(abi_name.text()));
+                    }
+                    continue;
+                }
+                ast::AsmPiece::AsmOptions(opt) => {
+                    opt.asm_options().for_each(|opt| {
+                        options |= match opt.syntax().first_token().map_or(T![$], |it| it.kind()) {
+                            T![att_syntax] => AsmOptions::ATT_SYNTAX,
+                            T![may_unwind] => AsmOptions::MAY_UNWIND,
+                            T![nomem] => AsmOptions::NOMEM,
+                            T![noreturn] => AsmOptions::NORETURN,
+                            T![nostack] => AsmOptions::NOSTACK,
+                            T![preserves_flags] => AsmOptions::PRESERVES_FLAGS,
+                            T![pure] => AsmOptions::PURE,
+                            T![raw] => AsmOptions::RAW,
+                            T![readonly] => AsmOptions::READONLY,
+                            _ => return,
+                        }
+                    });
+                    continue;
+                }
+                ast::AsmPiece::AsmOperandNamed(op) => {
+                    let name = op.name().map(|name| Symbol::intern(&name.text()));
+                    if let Some(name) = &name {
+                        named_args.insert(name.clone(), slot);
+                        named_pos.insert(slot, name.clone());
+                    }
+                    let Some(op) = op.asm_operand() else { continue };
+                    (
+                        name.map(Name::new_symbol_root),
+                        match op {
+                            ast::AsmOperand::AsmRegOperand(op) => {
+                                let Some(dir_spec) = op.asm_dir_spec() else {
+                                    continue;
+                                };
+                                let Some(reg) = lower_reg(op.asm_reg_spec()) else {
+                                    continue;
+                                };
+                                if dir_spec.in_token().is_some() {
+                                    let expr = self.collect_expr_opt(
+                                        op.asm_operand_expr().and_then(|it| it.in_expr()),
+                                    );
+                                    AsmOperand::In { reg, expr }
+                                } else if dir_spec.out_token().is_some() {
+                                    let expr = self.collect_expr_opt(
+                                        op.asm_operand_expr().and_then(|it| it.in_expr()),
+                                    );
+                                    AsmOperand::Out { reg, expr: Some(expr), late: false }
+                                } else if dir_spec.lateout_token().is_some() {
+                                    let expr = self.collect_expr_opt(
+                                        op.asm_operand_expr().and_then(|it| it.in_expr()),
+                                    );
+                                    AsmOperand::Out { reg, expr: Some(expr), late: true }
+                                } else if dir_spec.inout_token().is_some() {
+                                    let Some(op_expr) = op.asm_operand_expr() else { continue };
+                                    let in_expr = self.collect_expr_opt(op_expr.in_expr());
+                                    let out_expr =
+                                        op_expr.out_expr().map(|it| self.collect_expr(it));
+                                    match out_expr {
+                                        Some(out_expr) => AsmOperand::SplitInOut {
+                                            reg,
+                                            in_expr,
+                                            out_expr: Some(out_expr),
+                                            late: false,
+                                        },
+                                        None => {
+                                            AsmOperand::InOut { reg, expr: in_expr, late: false }
+                                        }
+                                    }
+                                } else if dir_spec.inlateout_token().is_some() {
+                                    let Some(op_expr) = op.asm_operand_expr() else { continue };
+                                    let in_expr = self.collect_expr_opt(op_expr.in_expr());
+                                    let out_expr =
+                                        op_expr.out_expr().map(|it| self.collect_expr(it));
+                                    match out_expr {
+                                        Some(out_expr) => AsmOperand::SplitInOut {
+                                            reg,
+                                            in_expr,
+                                            out_expr: Some(out_expr),
+                                            late: false,
+                                        },
+                                        None => {
+                                            AsmOperand::InOut { reg, expr: in_expr, late: false }
+                                        }
+                                    }
+                                } else {
+                                    continue;
+                                }
+                            }
+                            ast::AsmOperand::AsmLabel(l) => {
+                                AsmOperand::Label(self.collect_block_opt(l.block_expr()))
+                            }
+                            ast::AsmOperand::AsmConst(c) => {
+                                AsmOperand::Const(self.collect_expr_opt(c.expr()))
+                            }
+                            ast::AsmOperand::AsmSym(s) => {
+                                let Some(path) =
+                                    s.path().and_then(|p| self.expander.parse_path(self.db, p))
+                                else {
+                                    continue;
+                                };
+                                AsmOperand::Sym(path)
+                            }
+                        },
+                    )
+                }
+            };
+            operands.push(op);
+        }
+
+        let mut mappings = vec![];
+        let mut curarg = 0;
+        if !options.contains(AsmOptions::RAW) {
+            // Don't treat raw asm as a format string.
+            asm.template()
+                .filter_map(|it| Some((it.clone(), self.expand_macros_to_string(it)?)))
+                .for_each(|(expr, (s, is_direct_literal))| {
+                    let Ok(text) = s.value() else {
+                        return;
+                    };
+                    let template_snippet = match expr {
+                        ast::Expr::Literal(literal) => match literal.kind() {
+                            ast::LiteralKind::String(s) => Some(s.text().to_owned()),
+                            _ => None,
+                        },
+                        _ => None,
+                    };
+                    let str_style = match s.quote_offsets() {
+                        Some(offsets) => {
+                            let raw = usize::from(offsets.quotes.0.len()) - 1;
+                            // subtract 1 for the `r` prefix
+                            (raw != 0).then(|| raw - 1)
+                        }
+                        None => None,
+                    };
+
+                    let mut parser = rustc_parse_format::Parser::new(
+                        &text,
+                        str_style,
+                        template_snippet,
+                        false,
+                        rustc_parse_format::ParseMode::InlineAsm,
+                    );
+                    parser.curarg = curarg;
+
+                    let mut unverified_pieces = Vec::new();
+                    while let Some(piece) = parser.next() {
+                        if !parser.errors.is_empty() {
+                            break;
+                        } else {
+                            unverified_pieces.push(piece);
+                        }
+                    }
+
+                    curarg = parser.curarg;
+
+                    let to_span = |inner_span: rustc_parse_format::InnerSpan| {
+                        is_direct_literal.then(|| {
+                            TextRange::new(
+                                inner_span.start.try_into().unwrap(),
+                                inner_span.end.try_into().unwrap(),
+                            ) - TextSize::from(str_style.map(|it| it + 1).unwrap_or(0) as u32 + 1)
+                        })
+                    };
+                    for piece in unverified_pieces {
+                        match piece {
+                            rustc_parse_format::Piece::String(_) => {}
+                            rustc_parse_format::Piece::NextArgument(arg) => {
+                                // let span = arg_spans.next();
+
+                                let (operand_idx, _name) = match arg.position {
+                                    rustc_parse_format::ArgumentIs(idx)
+                                    | rustc_parse_format::ArgumentImplicitlyIs(idx) => {
+                                        if idx >= operands.len()
+                                            || named_pos.contains_key(&idx)
+                                            || reg_args.contains(&idx)
+                                        {
+                                            (None, None)
+                                        } else {
+                                            (Some(idx), None)
+                                        }
+                                    }
+                                    rustc_parse_format::ArgumentNamed(name) => {
+                                        let name = Symbol::intern(name);
+                                        (
+                                            named_args.get(&name).copied(),
+                                            Some(Name::new_symbol_root(name)),
+                                        )
+                                    }
+                                };
+
+                                if let Some(operand_idx) = operand_idx {
+                                    if let Some(position_span) = to_span(arg.position_span) {
+                                        mappings.push((position_span, operand_idx));
+                                    }
+                                }
+                            }
+                        }
+                    }
+                })
+        };
+        let idx = self.alloc_expr(
+            Expr::InlineAsm(InlineAsm { operands: operands.into_boxed_slice(), options }),
+            syntax_ptr,
+        );
+        self.source_map.template_map.get_or_insert_with(Default::default).1.insert(idx, mappings);
+        idx
+    }
+}
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/hir.rs b/src/tools/rust-analyzer/crates/hir-def/src/hir.rs
index 86fd092603f..d9358a28822 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/hir.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/hir.rs
@@ -307,7 +307,120 @@ pub struct OffsetOf {
 
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct InlineAsm {
-    pub e: ExprId,
+    pub operands: Box<[(Option<Name>, AsmOperand)]>,
+    pub options: AsmOptions,
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Hash)]
+pub struct AsmOptions(u16);
+bitflags::bitflags! {
+    impl AsmOptions: u16 {
+        const PURE            = 1 << 0;
+        const NOMEM           = 1 << 1;
+        const READONLY        = 1 << 2;
+        const PRESERVES_FLAGS = 1 << 3;
+        const NORETURN        = 1 << 4;
+        const NOSTACK         = 1 << 5;
+        const ATT_SYNTAX      = 1 << 6;
+        const RAW             = 1 << 7;
+        const MAY_UNWIND      = 1 << 8;
+    }
+}
+
+impl AsmOptions {
+    pub const COUNT: usize = Self::all().bits().count_ones() as usize;
+
+    pub const GLOBAL_OPTIONS: Self = Self::ATT_SYNTAX.union(Self::RAW);
+    pub const NAKED_OPTIONS: Self = Self::ATT_SYNTAX.union(Self::RAW).union(Self::NORETURN);
+
+    pub fn human_readable_names(&self) -> Vec<&'static str> {
+        let mut options = vec![];
+
+        if self.contains(AsmOptions::PURE) {
+            options.push("pure");
+        }
+        if self.contains(AsmOptions::NOMEM) {
+            options.push("nomem");
+        }
+        if self.contains(AsmOptions::READONLY) {
+            options.push("readonly");
+        }
+        if self.contains(AsmOptions::PRESERVES_FLAGS) {
+            options.push("preserves_flags");
+        }
+        if self.contains(AsmOptions::NORETURN) {
+            options.push("noreturn");
+        }
+        if self.contains(AsmOptions::NOSTACK) {
+            options.push("nostack");
+        }
+        if self.contains(AsmOptions::ATT_SYNTAX) {
+            options.push("att_syntax");
+        }
+        if self.contains(AsmOptions::RAW) {
+            options.push("raw");
+        }
+        if self.contains(AsmOptions::MAY_UNWIND) {
+            options.push("may_unwind");
+        }
+
+        options
+    }
+}
+
+impl std::fmt::Debug for AsmOptions {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        bitflags::parser::to_writer(self, f)
+    }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+pub enum AsmOperand {
+    In {
+        reg: InlineAsmRegOrRegClass,
+        expr: ExprId,
+    },
+    Out {
+        reg: InlineAsmRegOrRegClass,
+        expr: Option<ExprId>,
+        late: bool,
+    },
+    InOut {
+        reg: InlineAsmRegOrRegClass,
+        expr: ExprId,
+        late: bool,
+    },
+    SplitInOut {
+        reg: InlineAsmRegOrRegClass,
+        in_expr: ExprId,
+        out_expr: Option<ExprId>,
+        late: bool,
+    },
+    Label(ExprId),
+    Const(ExprId),
+    Sym(Path),
+}
+
+impl AsmOperand {
+    pub fn reg(&self) -> Option<&InlineAsmRegOrRegClass> {
+        match self {
+            Self::In { reg, .. }
+            | Self::Out { reg, .. }
+            | Self::InOut { reg, .. }
+            | Self::SplitInOut { reg, .. } => Some(reg),
+            Self::Const { .. } | Self::Sym { .. } | Self::Label { .. } => None,
+        }
+    }
+
+    pub fn is_clobber(&self) -> bool {
+        matches!(self, AsmOperand::Out { reg: InlineAsmRegOrRegClass::Reg(_), late: _, expr: None })
+    }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+pub enum InlineAsmRegOrRegClass {
+    Reg(Symbol),
+    RegClass(Symbol),
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -372,7 +485,21 @@ impl Expr {
         match self {
             Expr::Missing => {}
             Expr::Path(_) | Expr::OffsetOf(_) => {}
-            Expr::InlineAsm(it) => f(it.e),
+            Expr::InlineAsm(it) => it.operands.iter().for_each(|(_, op)| match op {
+                AsmOperand::In { expr, .. }
+                | AsmOperand::Out { expr: Some(expr), .. }
+                | AsmOperand::InOut { expr, .. } => f(*expr),
+                AsmOperand::SplitInOut { in_expr, out_expr, .. } => {
+                    f(*in_expr);
+                    if let Some(out_expr) = out_expr {
+                        f(*out_expr);
+                    }
+                }
+                AsmOperand::Out { expr: None, .. }
+                | AsmOperand::Const(_)
+                | AsmOperand::Label(_)
+                | AsmOperand::Sym(_) => (),
+            }),
             Expr::If { condition, then_branch, else_branch } => {
                 f(*condition);
                 f(*then_branch);
diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
index b6dbba12cd6..37ae1ab39b4 100644
--- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
+++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
@@ -50,11 +50,7 @@ fn main() {
     let i: u64 = 3;
     let o: u64;
     unsafe {
-        builtin #asm ( {
-            $crate::format_args!("mov {0}, {1}");
-            $crate::format_args!("add {0}, 5");
-        }
-        );
+        builtin #asm ("mov {0}, {1}", "add {0}, 5", out (reg)o, in (reg)i, );
     }
 }
 "##]],
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs
index 795d9b14df9..15fed45caf0 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs
@@ -119,9 +119,8 @@ register_builtin! {
     (module_path, ModulePath) => module_path_expand,
     (assert, Assert) => assert_expand,
     (stringify, Stringify) => stringify_expand,
-    (llvm_asm, LlvmAsm) => asm_expand,
     (asm, Asm) => asm_expand,
-    (global_asm, GlobalAsm) => global_asm_expand,
+    (global_asm, GlobalAsm) => asm_expand,
     (cfg, Cfg) => cfg_expand,
     (core_panic, CorePanic) => panic_expand,
     (std_panic, StdPanic) => panic_expand,
@@ -324,40 +323,15 @@ fn asm_expand(
     tt: &tt::Subtree,
     span: Span,
 ) -> ExpandResult<tt::Subtree> {
-    // We expand all assembly snippets to `format_args!` invocations to get format syntax
-    // highlighting for them.
-    let mut literals = Vec::new();
-    for tt in tt.token_trees.chunks(2) {
-        match tt {
-            [tt::TokenTree::Leaf(tt::Leaf::Literal(lit))]
-            | [tt::TokenTree::Leaf(tt::Leaf::Literal(lit)), tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', span: _, spacing: _ }))] =>
-            {
-                let dollar_krate = dollar_crate(span);
-                literals.push(quote!(span=>#dollar_krate::format_args!(#lit);));
-            }
-            _ => break,
-        }
-    }
-
+    let mut tt = tt.clone();
+    tt.delimiter.kind = tt::DelimiterKind::Parenthesis;
     let pound = mk_pound(span);
     let expanded = quote! {span =>
-        builtin #pound asm (
-            {##literals}
-        )
+        builtin #pound asm #tt
     };
     ExpandResult::ok(expanded)
 }
 
-fn global_asm_expand(
-    _db: &dyn ExpandDatabase,
-    _id: MacroCallId,
-    _tt: &tt::Subtree,
-    span: Span,
-) -> ExpandResult<tt::Subtree> {
-    // Expand to nothing (at item-level)
-    ExpandResult::ok(quote! {span =>})
-}
-
 fn cfg_expand(
     db: &dyn ExpandDatabase,
     id: MacroCallId,
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs
index 36327d1d49c..5cad08b9395 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/closure.rs
@@ -10,7 +10,10 @@ use chalk_ir::{
 use either::Either;
 use hir_def::{
     data::adt::VariantData,
-    hir::{Array, BinaryOp, BindingId, CaptureBy, Expr, ExprId, Pat, PatId, Statement, UnaryOp},
+    hir::{
+        Array, AsmOperand, BinaryOp, BindingId, CaptureBy, Expr, ExprId, Pat, PatId, Statement,
+        UnaryOp,
+    },
     lang_item::LangItem,
     resolver::{resolver_for_expr, ResolveValueResult, ValueNs},
     DefWithBodyId, FieldId, HasModule, TupleFieldId, TupleId, VariantId,
@@ -666,7 +669,21 @@ impl InferenceContext<'_> {
     fn walk_expr_without_adjust(&mut self, tgt_expr: ExprId) {
         match &self.body[tgt_expr] {
             Expr::OffsetOf(_) => (),
-            Expr::InlineAsm(e) => self.walk_expr_without_adjust(e.e),
+            Expr::InlineAsm(e) => e.operands.iter().for_each(|(_, op)| match op {
+                AsmOperand::In { expr, .. }
+                | AsmOperand::Out { expr: Some(expr), .. }
+                | AsmOperand::InOut { expr, .. } => self.walk_expr_without_adjust(*expr),
+                AsmOperand::SplitInOut { in_expr, out_expr, .. } => {
+                    self.walk_expr_without_adjust(*in_expr);
+                    if let Some(out_expr) = out_expr {
+                        self.walk_expr_without_adjust(*out_expr);
+                    }
+                }
+                AsmOperand::Out { expr: None, .. }
+                | AsmOperand::Const(_)
+                | AsmOperand::Label(_)
+                | AsmOperand::Sym(_) => (),
+            }),
             Expr::If { condition, then_branch, else_branch } => {
                 self.consume_expr(*condition);
                 self.consume_expr(*then_branch);
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs
index b1c793a1e38..a82d091cbb0 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs
@@ -9,7 +9,8 @@ use chalk_ir::{cast::Cast, fold::Shift, DebruijnIndex, Mutability, TyVariableKin
 use either::Either;
 use hir_def::{
     hir::{
-        ArithOp, Array, BinaryOp, ClosureKind, Expr, ExprId, LabelId, Literal, Statement, UnaryOp,
+        ArithOp, Array, AsmOperand, AsmOptions, BinaryOp, ClosureKind, Expr, ExprId, LabelId,
+        Literal, Statement, UnaryOp,
     },
     lang_item::{LangItem, LangItemTarget},
     path::{GenericArg, GenericArgs, Path},
@@ -41,9 +42,9 @@ use crate::{
     primitive::{self, UintTy},
     static_lifetime, to_chalk_trait_id,
     traits::FnTrait,
-    Adjust, Adjustment, AdtId, AutoBorrow, Binders, CallableDefId, FnAbi, FnPointer, FnSig,
-    FnSubst, Interner, Rawness, Scalar, Substitution, TraitEnvironment, TraitRef, Ty, TyBuilder,
-    TyExt, TyKind,
+    Adjust, Adjustment, AdtId, AutoBorrow, Binders, CallableDefId, CallableSig, FnAbi, FnPointer,
+    FnSig, FnSubst, Interner, Rawness, Scalar, Substitution, TraitEnvironment, TraitRef, Ty,
+    TyBuilder, TyExt, TyKind,
 };
 
 use super::{
@@ -924,9 +925,61 @@ impl InferenceContext<'_> {
                 expected
             }
             Expr::OffsetOf(_) => TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner),
-            Expr::InlineAsm(it) => {
-                self.infer_expr_no_expect(it.e);
-                self.result.standard_types.unit.clone()
+            Expr::InlineAsm(asm) => {
+                let mut check_expr_asm_operand = |expr, is_input: bool| {
+                    let ty = self.infer_expr_no_expect(expr);
+
+                    // If this is an input value, we require its type to be fully resolved
+                    // at this point. This allows us to provide helpful coercions which help
+                    // pass the type candidate list in a later pass.
+                    //
+                    // We don't require output types to be resolved at this point, which
+                    // allows them to be inferred based on how they are used later in the
+                    // function.
+                    if is_input {
+                        let ty = self.resolve_ty_shallow(&ty);
+                        match ty.kind(Interner) {
+                            TyKind::FnDef(def, parameters) => {
+                                let fnptr_ty = TyKind::Function(
+                                    CallableSig::from_def(self.db, *def, parameters).to_fn_ptr(),
+                                )
+                                .intern(Interner);
+                                _ = self.coerce(Some(expr), &ty, &fnptr_ty);
+                            }
+                            TyKind::Ref(mutbl, _, base_ty) => {
+                                let ptr_ty = TyKind::Raw(*mutbl, base_ty.clone()).intern(Interner);
+                                _ = self.coerce(Some(expr), &ty, &ptr_ty);
+                            }
+                            _ => {}
+                        }
+                    }
+                };
+
+                let diverge = asm.options.contains(AsmOptions::NORETURN);
+                asm.operands.iter().for_each(|(_, operand)| match *operand {
+                    AsmOperand::In { expr, .. } => check_expr_asm_operand(expr, true),
+                    AsmOperand::Out { expr: Some(expr), .. } | AsmOperand::InOut { expr, .. } => {
+                        check_expr_asm_operand(expr, false)
+                    }
+                    AsmOperand::Out { expr: None, .. } => (),
+                    AsmOperand::SplitInOut { in_expr, out_expr, .. } => {
+                        check_expr_asm_operand(in_expr, true);
+                        if let Some(out_expr) = out_expr {
+                            check_expr_asm_operand(out_expr, false);
+                        }
+                    }
+                    // FIXME
+                    AsmOperand::Label(_) => (),
+                    // FIXME
+                    AsmOperand::Const(_) => (),
+                    // FIXME
+                    AsmOperand::Sym(_) => (),
+                });
+                if diverge {
+                    self.result.standard_types.never.clone()
+                } else {
+                    self.result.standard_types.unit.clone()
+                }
             }
         };
         // use a new type variable if we got unknown here
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs
index 7fed5f0203b..8e52725e536 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/mutability.rs
@@ -3,7 +3,9 @@
 
 use chalk_ir::{cast::Cast, Mutability};
 use hir_def::{
-    hir::{Array, BinaryOp, BindingAnnotation, Expr, ExprId, PatId, Statement, UnaryOp},
+    hir::{
+        Array, AsmOperand, BinaryOp, BindingAnnotation, Expr, ExprId, PatId, Statement, UnaryOp,
+    },
     lang_item::LangItem,
 };
 use hir_expand::name::Name;
@@ -39,7 +41,25 @@ impl InferenceContext<'_> {
     fn infer_mut_expr_without_adjust(&mut self, tgt_expr: ExprId, mutability: Mutability) {
         match &self.body[tgt_expr] {
             Expr::Missing => (),
-            Expr::InlineAsm(e) => self.infer_mut_expr_without_adjust(e.e, Mutability::Not),
+            Expr::InlineAsm(e) => {
+                e.operands.iter().for_each(|(_, op)| match op {
+                    AsmOperand::In { expr, .. }
+                    | AsmOperand::Out { expr: Some(expr), .. }
+                    | AsmOperand::InOut { expr, .. } => {
+                        self.infer_mut_expr_without_adjust(*expr, Mutability::Not)
+                    }
+                    AsmOperand::SplitInOut { in_expr, out_expr, .. } => {
+                        self.infer_mut_expr_without_adjust(*in_expr, Mutability::Not);
+                        if let Some(out_expr) = out_expr {
+                            self.infer_mut_expr_without_adjust(*out_expr, Mutability::Not);
+                        }
+                    }
+                    AsmOperand::Out { expr: None, .. }
+                    | AsmOperand::Label(_)
+                    | AsmOperand::Sym(_)
+                    | AsmOperand::Const(_) => (),
+                });
+            }
             Expr::OffsetOf(_) => (),
             &Expr::If { condition, then_branch, else_branch } => {
                 self.infer_mut_expr(condition, Mutability::Not);
@@ -129,7 +149,7 @@ impl InferenceContext<'_> {
                                     target,
                                 }) = base_adjustments
                                 {
-                                    // For assignee exprs `IndexMut` obiligations are already applied
+                                    // For assignee exprs `IndexMut` obligations are already applied
                                     if !is_assignee_expr {
                                         if let TyKind::Ref(_, _, ty) = target.kind(Interner) {
                                             base_ty = Some(ty.clone());
diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs
index 5a6161f9428..10ab6d3ff89 100644
--- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs
+++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/macros.rs
@@ -1,7 +1,7 @@
 use expect_test::expect;
 use test_utils::{bench, bench_fixture, skip_slow_tests};
 
-use crate::tests::check_infer_with_mismatches;
+use crate::tests::{check_infer_with_mismatches, check_no_mismatches};
 
 use super::{check_infer, check_types};
 
@@ -1406,3 +1406,100 @@ fn foo(t: Tensor) {
 "#,
     );
 }
+
+#[test]
+fn asm_unit() {
+    check_no_mismatches(
+        r#"
+//- minicore: asm
+fn unit() {
+    core::arch::asm!("")
+}
+"#,
+    );
+}
+
+#[test]
+fn asm_no_return() {
+    check_no_mismatches(
+        r#"
+//- minicore: asm
+fn unit() -> ! {
+    core::arch::asm!("", options(noreturn))
+}
+"#,
+    );
+}
+
+#[test]
+fn asm_things() {
+    check_infer(
+        r#"
+//- minicore: asm, concat
+fn main() {
+    unsafe {
+        let foo = 1;
+        let mut o = 0;
+        core::arch::asm!(
+            "%input = OpLoad _ {0}",
+            concat!("%result = ", bar, " _ %input"),
+            "OpStore {1} %result",
+            in(reg) &foo,
+            in(reg) &mut o,
+        );
+        o
+
+        let thread_id: usize;
+        core::arch::asm!("
+            mov {0}, gs:[0x30]
+            mov {0}, [{0}+0x48]
+        ", out(reg) thread_id, options(pure, readonly, nostack));
+
+        static UNMAP_BASE: usize;
+        const MEM_RELEASE: usize;
+        static VirtualFree: usize;
+        const OffPtr: usize;
+        const OffFn: usize;
+        core::arch::asm!("
+            push {free_type}
+            push {free_size}
+            push {base}
+
+            mov eax, fs:[30h]
+            mov eax, [eax+8h]
+            add eax, {off_fn}
+            mov [eax-{off_fn}+{off_ptr}], eax
+
+            push eax
+
+            jmp {virtual_free}
+            ",
+            off_ptr = const OffPtr,
+            off_fn  = const OffFn,
+
+            free_size = const 0,
+            free_type = const MEM_RELEASE,
+
+            virtual_free = sym VirtualFree,
+
+            base = sym UNMAP_BASE,
+            options(noreturn),
+        );
+    }
+}
+"#,
+        expect![[r#"
+            !0..122 'builti...muto,)': ()
+            !0..136 'builti...tack))': ()
+            !0..449 'builti...urn),)': !
+            10..1236 '{     ...   } }': ()
+            16..1234 'unsafe...     }': ()
+            37..40 'foo': i32
+            43..44 '1': i32
+            58..63 'mut o': i32
+            66..67 '0': i32
+            293..294 'o': i32
+            308..317 'thread_id': usize
+        "#]],
+    )
+}
diff --git a/src/tools/rust-analyzer/crates/hir/src/has_source.rs b/src/tools/rust-analyzer/crates/hir/src/has_source.rs
index 7d52a28b91e..82c90ac3010 100644
--- a/src/tools/rust-analyzer/crates/hir/src/has_source.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/has_source.rs
@@ -14,8 +14,8 @@ use tt::TextRange;
 
 use crate::{
     db::HirDatabase, Adt, Callee, Const, Enum, ExternCrateDecl, Field, FieldSource, Function, Impl,
-    Label, LifetimeParam, LocalSource, Macro, Module, Param, SelfParam, Static, Struct, Trait,
-    TraitAlias, TypeAlias, TypeOrConstParam, Union, Variant,
+    InlineAsmOperand, Label, LifetimeParam, LocalSource, Macro, Module, Param, SelfParam, Static,
+    Struct, Trait, TraitAlias, TypeAlias, TypeOrConstParam, Union, Variant,
 };
 
 pub trait HasSource {
@@ -292,3 +292,26 @@ impl HasSource for ExternCrateDecl {
         Some(self.id.lookup(db.upcast()).source(db.upcast()))
     }
 }
+
+impl HasSource for InlineAsmOperand {
+    type Ast = ast::AsmOperandNamed;
+    fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
+        let (_body, source_map) = db.body_with_source_map(self.owner);
+        if let Ok(src) = source_map.expr_syntax(self.expr) {
+            let root = src.file_syntax(db.upcast());
+            return src
+                .map(|ast| match ast.to_node(&root) {
+                    ast::Expr::AsmExpr(asm) => asm
+                        .asm_pieces()
+                        .filter_map(|it| match it {
+                            ast::AsmPiece::AsmOperandNamed(it) => Some(it),
+                            _ => None,
+                        })
+                        .nth(self.index),
+                    _ => None,
+                })
+                .transpose();
+        }
+        None
+    }
+}
diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs
index 81d6466c2f3..3b8b901fa14 100644
--- a/src/tools/rust-analyzer/crates/hir/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs
@@ -43,7 +43,7 @@ use hir_def::{
     body::{BodyDiagnostic, SyntheticSyntax},
     data::adt::VariantData,
     generics::{LifetimeParamData, TypeOrConstParamData, TypeParamProvenance},
-    hir::{BindingAnnotation, BindingId, ExprOrPatId, LabelId, Pat},
+    hir::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, LabelId, Pat},
     item_tree::{AttrOwner, FieldParent, ItemTreeFieldId, ItemTreeNode},
     lang_item::LangItemTarget,
     layout::{self, ReprOptions, TargetDataLayout},
@@ -5246,6 +5246,26 @@ impl Type {
     }
 }
 
+#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
+pub struct InlineAsmOperand {
+    owner: DefWithBodyId,
+    expr: ExprId,
+    index: usize,
+}
+
+impl InlineAsmOperand {
+    pub fn parent(self, _db: &dyn HirDatabase) -> DefWithBody {
+        self.owner.into()
+    }
+
+    pub fn name(&self, db: &dyn HirDatabase) -> Option<Name> {
+        match &db.body(self.owner)[self.expr] {
+            hir_def::hir::Expr::InlineAsm(e) => e.operands.get(self.index)?.0.clone(),
+            _ => None,
+        }
+    }
+}
+
 // FIXME: Document this
 #[derive(Debug)]
 pub struct Callable {
diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs
index c78b59826c9..0ba0e446578 100644
--- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs
@@ -47,9 +47,9 @@ use crate::{
     source_analyzer::{resolve_hir_path, SourceAnalyzer},
     Access, Adjust, Adjustment, Adt, AutoBorrow, BindingMode, BuiltinAttr, Callable, Const,
     ConstParam, Crate, DeriveHelper, Enum, Field, Function, HasSource, HirFileId, Impl, InFile,
-    ItemInNs, Label, LifetimeParam, Local, Macro, Module, ModuleDef, Name, OverloadedDeref, Path,
-    ScopeDef, Static, Struct, ToolModule, Trait, TraitAlias, TupleField, Type, TypeAlias,
-    TypeParam, Union, Variant, VariantDef,
+    InlineAsmOperand, ItemInNs, Label, LifetimeParam, Local, Macro, Module, ModuleDef, Name,
+    OverloadedDeref, Path, ScopeDef, Static, Struct, ToolModule, Trait, TraitAlias, TupleField,
+    Type, TypeAlias, TypeParam, Union, Variant, VariantDef,
 };
 
 const CONTINUE_NO_BREAKS: ControlFlow<Infallible, ()> = ControlFlow::Continue(());
@@ -368,7 +368,6 @@ impl<'db> SemanticsImpl<'db> {
                     | BuiltinFnLikeExpander::File
                     | BuiltinFnLikeExpander::ModulePath
                     | BuiltinFnLikeExpander::Asm
-                    | BuiltinFnLikeExpander::LlvmAsm
                     | BuiltinFnLikeExpander::GlobalAsm
                     | BuiltinFnLikeExpander::LogSyntax
                     | BuiltinFnLikeExpander::TraceMacros
@@ -547,11 +546,11 @@ impl<'db> SemanticsImpl<'db> {
         )
     }
 
-    /// Retrieves all the formatting parts of the format_args! template string.
+    /// Retrieves all the formatting parts of the format_args! (or `asm!`) template string.
     pub fn as_format_args_parts(
         &self,
         string: &ast::String,
-    ) -> Option<Vec<(TextRange, Option<PathResolution>)>> {
+    ) -> Option<Vec<(TextRange, Option<Either<PathResolution, InlineAsmOperand>>)>> {
         let quote = string.open_quote_text_range()?;
 
         let token = self.wrap_token_infile(string.syntax().clone()).into_real_file().ok()?;
@@ -561,14 +560,31 @@ impl<'db> SemanticsImpl<'db> {
                 let string = ast::String::cast(token)?;
                 let literal =
                     string.syntax().parent().filter(|it| it.kind() == SyntaxKind::LITERAL)?;
-                let format_args = ast::FormatArgsExpr::cast(literal.parent()?)?;
-                let source_analyzer = self.analyze_no_infer(format_args.syntax())?;
-                let format_args = self.wrap_node_infile(format_args);
-                let res = source_analyzer
-                    .as_format_args_parts(self.db, format_args.as_ref())?
-                    .map(|(range, res)| (range + quote.end(), res))
-                    .collect();
-                Some(res)
+                let parent = literal.parent()?;
+                if let Some(format_args) = ast::FormatArgsExpr::cast(parent.clone()) {
+                    let source_analyzer = self.analyze_no_infer(format_args.syntax())?;
+                    let format_args = self.wrap_node_infile(format_args);
+                    let res = source_analyzer
+                        .as_format_args_parts(self.db, format_args.as_ref())?
+                        .map(|(range, res)| (range + quote.end(), res.map(Either::Left)))
+                        .collect();
+                    Some(res)
+                } else {
+                    let asm = ast::AsmExpr::cast(parent)?;
+                    let source_analyzer = self.analyze_no_infer(asm.syntax())?;
+                    let asm = self.wrap_node_infile(asm);
+                    let (owner, (expr, asm_parts)) = source_analyzer.as_asm_parts(asm.as_ref())?;
+                    let res = asm_parts
+                        .iter()
+                        .map(|&(range, index)| {
+                            (
+                                range + quote.end(),
+                                Some(Either::Right(InlineAsmOperand { owner, expr, index })),
+                            )
+                        })
+                        .collect();
+                    Some(res)
+                }
             })()
             .map_or(ControlFlow::Continue(()), ControlFlow::Break)
         })
@@ -579,7 +595,7 @@ impl<'db> SemanticsImpl<'db> {
         &self,
         original_token: SyntaxToken,
         offset: TextSize,
-    ) -> Option<(TextRange, Option<PathResolution>)> {
+    ) -> Option<(TextRange, Option<Either<PathResolution, InlineAsmOperand>>)> {
         let original_string = ast::String::cast(original_token.clone())?;
         let original_token = self.wrap_token_infile(original_token).into_real_file().ok()?;
         let quote = original_string.open_quote_text_range()?;
@@ -600,13 +616,26 @@ impl<'db> SemanticsImpl<'db> {
         &self,
         string: ast::String,
         offset: TextSize,
-    ) -> Option<(TextRange, Option<PathResolution>)> {
+    ) -> Option<(TextRange, Option<Either<PathResolution, InlineAsmOperand>>)> {
         debug_assert!(offset <= string.syntax().text_range().len());
         let literal = string.syntax().parent().filter(|it| it.kind() == SyntaxKind::LITERAL)?;
-        let format_args = ast::FormatArgsExpr::cast(literal.parent()?)?;
-        let source_analyzer = &self.analyze_no_infer(format_args.syntax())?;
-        let format_args = self.wrap_node_infile(format_args);
-        source_analyzer.resolve_offset_in_format_args(self.db, format_args.as_ref(), offset)
+        let parent = literal.parent()?;
+        if let Some(format_args) = ast::FormatArgsExpr::cast(parent.clone()) {
+            let source_analyzer = &self.analyze_no_infer(format_args.syntax())?;
+            let format_args = self.wrap_node_infile(format_args);
+            source_analyzer
+                .resolve_offset_in_format_args(self.db, format_args.as_ref(), offset)
+                .map(|(range, res)| (range, res.map(Either::Left)))
+        } else {
+            let asm = ast::AsmExpr::cast(parent)?;
+            let source_analyzer = &self.analyze_no_infer(asm.syntax())?;
+            let asm = self.wrap_node_infile(asm);
+            source_analyzer.resolve_offset_in_asm_template(asm.as_ref(), offset).map(
+                |(owner, (expr, range, index))| {
+                    (range, Some(Either::Right(InlineAsmOperand { owner, expr, index })))
+                },
+            )
+        }
     }
 
     /// Maps a node down by mapping its first and last token down.
@@ -1782,6 +1811,7 @@ to_def_impls![
     (crate::Label, ast::Label, label_to_def),
     (crate::Adt, ast::Adt, adt_to_def),
     (crate::ExternCrateDecl, ast::ExternCrate, extern_crate_to_def),
+    (crate::InlineAsmOperand, ast::AsmOperandNamed, asm_operand_to_def),
     (MacroCallId, ast::MacroCall, macro_call_to_macro_call),
 ];
 
diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs
index edeb19030ac..fc629b9c6d0 100644
--- a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs
@@ -110,7 +110,7 @@ use syntax::{
     AstNode, AstPtr, SyntaxNode,
 };
 
-use crate::{db::HirDatabase, InFile};
+use crate::{db::HirDatabase, InFile, InlineAsmOperand};
 
 #[derive(Default)]
 pub(super) struct SourceToDefCache {
@@ -273,6 +273,25 @@ impl SourceToDefCtx<'_, '_> {
             ast::Adt::Union(it) => self.union_to_def(InFile::new(file_id, it)).map(AdtId::UnionId),
         }
     }
+
+    pub(super) fn asm_operand_to_def(
+        &mut self,
+        src: InFile<&ast::AsmOperandNamed>,
+    ) -> Option<InlineAsmOperand> {
+        let asm = src.value.syntax().parent().and_then(ast::AsmExpr::cast)?;
+        let index = asm
+            .asm_pieces()
+            .filter_map(|it| match it {
+                ast::AsmPiece::AsmOperandNamed(it) => Some(it),
+                _ => None,
+            })
+            .position(|it| it == *src.value)?;
+        let container = self.find_pat_or_label_container(src.syntax_ref())?;
+        let (_, source_map) = self.db.body_with_source_map(container);
+        let expr = source_map.node_expr(src.with_value(&ast::Expr::AsmExpr(asm)))?;
+        Some(InlineAsmOperand { owner: container, expr, index })
+    }
+
     pub(super) fn bind_pat_to_def(
         &mut self,
         src: InFile<&ast::IdentPat>,
diff --git a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs
index be0116862b9..f6f1da1b7d6 100644
--- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs
@@ -904,6 +904,20 @@ impl SourceAnalyzer {
         })
     }
 
+    pub(crate) fn resolve_offset_in_asm_template(
+        &self,
+        asm: InFile<&ast::AsmExpr>,
+        offset: TextSize,
+    ) -> Option<(DefWithBodyId, (ExprId, TextRange, usize))> {
+        let (def, _, body_source_map) = self.def.as_ref()?;
+        let (expr, args) = body_source_map.asm_template_args(asm)?;
+        Some(*def).zip(
+            args.iter()
+                .find(|(range, _)| range.contains_inclusive(offset))
+                .map(|(range, idx)| (expr, *range, *idx)),
+        )
+    }
+
     pub(crate) fn as_format_args_parts<'a>(
         &'a self,
         db: &'a dyn HirDatabase,
@@ -927,6 +941,14 @@ impl SourceAnalyzer {
         ))
     }
 
+    pub(crate) fn as_asm_parts(
+        &self,
+        asm: InFile<&ast::AsmExpr>,
+    ) -> Option<(DefWithBodyId, (ExprId, &[(TextRange, usize)]))> {
+        let (def, _, body_source_map) = self.def.as_ref()?;
+        Some(*def).zip(body_source_map.asm_template_args(asm))
+    }
+
     fn resolve_impl_method_or_trait_def(
         &self,
         db: &dyn HirDatabase,
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs
index f8c8b12bd25..8c97ebd5500 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs
@@ -360,6 +360,7 @@ impl CompletionItemKind {
                 SymbolKind::Field => "fd",
                 SymbolKind::Function => "fn",
                 SymbolKind::Impl => "im",
+                SymbolKind::InlineAsmRegOrRegClass => "ar",
                 SymbolKind::Label => "lb",
                 SymbolKind::LifetimeParam => "lt",
                 SymbolKind::Local => "lc",
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/defs.rs b/src/tools/rust-analyzer/crates/ide-db/src/defs.rs
index 5d4b1999088..099f26eba78 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/defs.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/defs.rs
@@ -10,8 +10,8 @@ use either::Either;
 use hir::{
     Adt, AsAssocItem, AsExternAssocItem, AssocItem, AttributeTemplate, BuiltinAttr, BuiltinType,
     Const, Crate, DefWithBody, DeriveHelper, DocLinkDef, ExternAssocItem, ExternCrateDecl, Field,
-    Function, GenericParam, HasVisibility, HirDisplay, Impl, Label, Local, Macro, Module,
-    ModuleDef, Name, PathResolution, Semantics, Static, StaticLifetime, ToolModule, Trait,
+    Function, GenericParam, HasVisibility, HirDisplay, Impl, InlineAsmOperand, Label, Local, Macro,
+    Module, ModuleDef, Name, PathResolution, Semantics, Static, StaticLifetime, ToolModule, Trait,
     TraitAlias, TupleField, TypeAlias, Variant, VariantDef, Visibility,
 };
 use span::Edition;
@@ -50,6 +50,8 @@ pub enum Definition {
     BuiltinAttr(BuiltinAttr),
     ToolModule(ToolModule),
     ExternCrateDecl(ExternCrateDecl),
+    InlineAsmRegOrRegClass(()),
+    InlineAsmOperand(InlineAsmOperand),
 }
 
 impl Definition {
@@ -83,11 +85,13 @@ impl Definition {
             Definition::Label(it) => it.module(db),
             Definition::ExternCrateDecl(it) => it.module(db),
             Definition::DeriveHelper(it) => it.derive().module(db),
+            Definition::InlineAsmOperand(it) => it.parent(db).module(db),
             Definition::BuiltinAttr(_)
             | Definition::BuiltinType(_)
             | Definition::BuiltinLifetime(_)
             | Definition::TupleField(_)
-            | Definition::ToolModule(_) => return None,
+            | Definition::ToolModule(_)
+            | Definition::InlineAsmRegOrRegClass(_) => return None,
         };
         Some(module)
     }
@@ -121,7 +125,9 @@ impl Definition {
             | Definition::Local(_)
             | Definition::GenericParam(_)
             | Definition::Label(_)
-            | Definition::DeriveHelper(_) => return None,
+            | Definition::DeriveHelper(_)
+            | Definition::InlineAsmRegOrRegClass(_)
+            | Definition::InlineAsmOperand(_) => return None,
         };
         Some(vis)
     }
@@ -150,6 +156,8 @@ impl Definition {
             Definition::ToolModule(_) => return None,  // FIXME
             Definition::DeriveHelper(it) => it.name(db),
             Definition::ExternCrateDecl(it) => return it.alias_or_name(db),
+            Definition::InlineAsmRegOrRegClass(_) => return None,
+            Definition::InlineAsmOperand(op) => return op.name(db),
         };
         Some(name)
     }
@@ -212,6 +220,7 @@ impl Definition {
             Definition::ToolModule(_) => None,
             Definition::DeriveHelper(_) => None,
             Definition::TupleField(_) => None,
+            Definition::InlineAsmRegOrRegClass(_) | Definition::InlineAsmOperand(_) => None,
         };
 
         docs.or_else(|| {
@@ -268,6 +277,9 @@ impl Definition {
             Definition::DeriveHelper(it) => {
                 format!("derive_helper {}", it.name(db).display(db, edition))
             }
+            // FIXME
+            Definition::InlineAsmRegOrRegClass(_) => "inline_asm_reg_or_reg_class".to_owned(),
+            Definition::InlineAsmOperand(_) => "inline_asm_reg_operand".to_owned(),
         }
     }
 }
@@ -429,7 +441,6 @@ impl NameClass {
         let _p = tracing::info_span!("NameClass::classify").entered();
 
         let parent = name.syntax().parent()?;
-
         let definition = match_ast! {
             match parent {
                 ast::Item(it) => classify_item(sema, it)?,
@@ -440,6 +451,7 @@ impl NameClass {
                 ast::Variant(it) => Definition::Variant(sema.to_def(&it)?),
                 ast::TypeParam(it) => Definition::GenericParam(sema.to_def(&it)?.into()),
                 ast::ConstParam(it) => Definition::GenericParam(sema.to_def(&it)?.into()),
+                ast::AsmOperandNamed(it) => Definition::InlineAsmOperand(sema.to_def(&it)?),
                 _ => return None,
             }
         };
@@ -699,6 +711,9 @@ impl NameRefClass {
                         NameRefClass::ExternCrateShorthand { krate, decl: extern_crate }
                     })
                 },
+                ast::AsmRegSpec(_) => {
+                    Some(NameRefClass::Definition(Definition::InlineAsmRegOrRegClass(())))
+                },
                 _ => None
             }
         }
@@ -753,6 +768,18 @@ impl From<Impl> for Definition {
     }
 }
 
+impl From<InlineAsmOperand> for Definition {
+    fn from(value: InlineAsmOperand) -> Self {
+        Definition::InlineAsmOperand(value)
+    }
+}
+
+impl From<Either<PathResolution, InlineAsmOperand>> for Definition {
+    fn from(value: Either<PathResolution, InlineAsmOperand>) -> Self {
+        value.either(Definition::from, Definition::from)
+    }
+}
+
 impl AsAssocItem for Definition {
     fn as_assoc_item(self, db: &dyn hir::db::HirDatabase) -> Option<AssocItem> {
         match self {
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs
index ab161f0ce57..3435757ad31 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs
@@ -224,6 +224,7 @@ pub enum SymbolKind {
     Function,
     Method,
     Impl,
+    InlineAsmRegOrRegClass,
     Label,
     LifetimeParam,
     Local,
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs
index 262eefeec00..f1404ed9f22 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs
@@ -200,12 +200,14 @@ impl Definition {
                         .and_then(syn_ctx_is_root)
                 }
             }
+            Definition::InlineAsmOperand(it) => name_range(it, sema).and_then(syn_ctx_is_root),
             Definition::BuiltinType(_)
             | Definition::BuiltinLifetime(_)
             | Definition::BuiltinAttr(_)
             | Definition::SelfType(_)
             | Definition::ToolModule(_)
-            | Definition::TupleField(_) => return None,
+            | Definition::TupleField(_)
+            | Definition::InlineAsmRegOrRegClass(_) => return None,
             // FIXME: This should be doable in theory
             Definition::DeriveHelper(_) => return None,
         };
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/search.rs b/src/tools/rust-analyzer/crates/ide-db/src/search.rs
index 12ce5a403fe..ad30c624c41 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs
@@ -8,10 +8,11 @@ use std::mem;
 use std::{cell::LazyCell, cmp::Reverse};
 
 use base_db::{salsa::Database, SourceDatabase, SourceRootDatabase};
+use either::Either;
 use hir::{
     sym, Adt, AsAssocItem, DefWithBody, FileRange, FileRangeWrapper, HasAttrs, HasContainer,
-    HasSource, HirFileIdExt, InFile, InFileWrapper, InRealFile, ItemContainer, ModuleSource,
-    PathResolution, Semantics, Visibility,
+    HasSource, HirFileIdExt, InFile, InFileWrapper, InRealFile, InlineAsmOperand, ItemContainer,
+    ModuleSource, PathResolution, Semantics, Visibility,
 };
 use memchr::memmem::Finder;
 use parser::SyntaxKind;
@@ -318,6 +319,23 @@ impl Definition {
             };
         }
 
+        if let Definition::InlineAsmOperand(op) = self {
+            let def = match op.parent(db) {
+                DefWithBody::Function(f) => f.source(db).map(|src| src.syntax().cloned()),
+                DefWithBody::Const(c) => c.source(db).map(|src| src.syntax().cloned()),
+                DefWithBody::Static(s) => s.source(db).map(|src| src.syntax().cloned()),
+                DefWithBody::Variant(v) => v.source(db).map(|src| src.syntax().cloned()),
+                // FIXME: implement
+                DefWithBody::InTypeConst(_) => return SearchScope::empty(),
+            };
+            return match def {
+                Some(def) => SearchScope::file_range(
+                    def.as_ref().original_file_range_with_macro_call_body(db),
+                ),
+                None => SearchScope::single_file(file_id),
+            };
+        }
+
         if let Definition::SelfType(impl_) = self {
             return match impl_.source(db).map(|src| src.syntax().cloned()) {
                 Some(def) => SearchScope::file_range(
@@ -908,7 +926,6 @@ impl<'a> FindUsages<'a> {
         let finder = &Finder::new(name);
         let include_self_kw_refs =
             self.include_self_kw_refs.as_ref().map(|ty| (ty, Finder::new("Self")));
-
         for (text, file_id, search_range) in Self::scope_files(sema.db, &search_scope) {
             self.sema.db.unwind_if_cancelled();
             let tree = LazyCell::new(move || sema.parse(file_id).syntax().clone());
@@ -917,7 +934,7 @@ impl<'a> FindUsages<'a> {
             for offset in Self::match_indices(&text, finder, search_range) {
                 tree.token_at_offset(offset).for_each(|token| {
                     let Some(str_token) = ast::String::cast(token.clone()) else { return };
-                    if let Some((range, nameres)) =
+                    if let Some((range, Some(nameres))) =
                         sema.check_for_format_args_template(token, offset)
                     {
                         if self.found_format_args_ref(file_id, range, str_token, nameres, sink) {}
@@ -1087,19 +1104,19 @@ impl<'a> FindUsages<'a> {
         file_id: EditionedFileId,
         range: TextRange,
         token: ast::String,
-        res: Option<PathResolution>,
+        res: Either<PathResolution, InlineAsmOperand>,
         sink: &mut dyn FnMut(EditionedFileId, FileReference) -> bool,
     ) -> bool {
-        match res.map(Definition::from) {
-            Some(def) if def == self.def => {
-                let reference = FileReference {
-                    range,
-                    name: FileReferenceNode::FormatStringEntry(token, range),
-                    category: ReferenceCategory::READ,
-                };
-                sink(file_id, reference)
-            }
-            _ => false,
+        let def = res.either(Definition::from, Definition::from);
+        if def == self.def {
+            let reference = FileReference {
+                range,
+                name: FileReferenceNode::FormatStringEntry(token, range),
+                category: ReferenceCategory::READ,
+            };
+            sink(file_id, reference)
+        } else {
+            false
         }
     }
 
diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs
index 925ae620231..ea16a11d56d 100644
--- a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs
@@ -219,7 +219,9 @@ pub(crate) fn resolve_doc_path_for_def(
         | Definition::Local(_)
         | Definition::GenericParam(_)
         | Definition::Label(_)
-        | Definition::DeriveHelper(_) => None,
+        | Definition::DeriveHelper(_)
+        | Definition::InlineAsmRegOrRegClass(_)
+        | Definition::InlineAsmOperand(_) => None,
     }
     .map(Definition::from)
 }
@@ -672,7 +674,9 @@ fn filename_and_frag_for_def(
         | Definition::BuiltinAttr(_)
         | Definition::BuiltinLifetime(_)
         | Definition::ToolModule(_)
-        | Definition::DeriveHelper(_) => return None,
+        | Definition::DeriveHelper(_)
+        | Definition::InlineAsmRegOrRegClass(_)
+        | Definition::InlineAsmOperand(_) => return None,
     };
 
     Some((def, res, None))
diff --git a/src/tools/rust-analyzer/crates/ide/src/moniker.rs b/src/tools/rust-analyzer/crates/ide/src/moniker.rs
index 4be1b570981..14781b21296 100644
--- a/src/tools/rust-analyzer/crates/ide/src/moniker.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/moniker.rs
@@ -223,11 +223,12 @@ pub(crate) fn def_to_kind(db: &RootDatabase, def: Definition) -> SymbolInformati
                 Variable
             }
         }
-        Definition::Label(..) => Variable, // For lack of a better variant
+        Definition::Label(..) | Definition::InlineAsmOperand(_) => Variable, // For lack of a better variant
         Definition::DeriveHelper(..) => Attribute,
         Definition::BuiltinAttr(..) => Attribute,
         Definition::ToolModule(..) => Module,
         Definition::ExternCrateDecl(..) => Module,
+        Definition::InlineAsmRegOrRegClass(..) => Module,
     }
 }
 
@@ -320,7 +321,9 @@ pub(crate) fn def_to_moniker(
         | Definition::DeriveHelper(_)
         | Definition::BuiltinLifetime(_)
         | Definition::BuiltinAttr(_)
-        | Definition::ToolModule(_) => return None,
+        | Definition::ToolModule(_)
+        | Definition::InlineAsmRegOrRegClass(_)
+        | Definition::InlineAsmOperand(_) => return None,
 
         Definition::Local(local) => {
             if !local.is_param(db) {
diff --git a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs
index 9ace9fda62b..9bc7bf411f0 100644
--- a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs
@@ -237,11 +237,13 @@ impl TryToNav for Definition {
             Definition::Trait(it) => it.try_to_nav(db),
             Definition::TraitAlias(it) => it.try_to_nav(db),
             Definition::TypeAlias(it) => it.try_to_nav(db),
-            Definition::ExternCrateDecl(it) => Some(it.try_to_nav(db)?),
+            Definition::ExternCrateDecl(it) => it.try_to_nav(db),
+            Definition::InlineAsmOperand(it) => it.try_to_nav(db),
             Definition::BuiltinLifetime(_)
             | Definition::BuiltinType(_)
             | Definition::TupleField(_)
             | Definition::ToolModule(_)
+            | Definition::InlineAsmRegOrRegClass(_)
             | Definition::BuiltinAttr(_) => None,
             // FIXME: The focus range should be set to the helper declaration
             Definition::DeriveHelper(it) => it.derive().try_to_nav(db),
@@ -693,6 +695,31 @@ impl TryToNav for hir::ConstParam {
     }
 }
 
+impl TryToNav for hir::InlineAsmOperand {
+    fn try_to_nav(&self, db: &RootDatabase) -> Option<UpmappingResult<NavigationTarget>> {
+        let InFile { file_id, value } = &self.source(db)?;
+        let file_id = *file_id;
+        Some(orig_range_with_focus(db, file_id, value.syntax(), value.name()).map(
+            |(FileRange { file_id, range: full_range }, focus_range)| {
+                let edition = self.parent(db).module(db).krate().edition(db);
+                NavigationTarget {
+                    file_id,
+                    name: self
+                        .name(db)
+                        .map_or_else(|| "_".into(), |it| it.display(db, edition).to_smolstr()),
+                    alias: None,
+                    kind: Some(SymbolKind::Local),
+                    full_range,
+                    focus_range,
+                    container_name: None,
+                    description: None,
+                    docs: None,
+                }
+            },
+        ))
+    }
+}
+
 #[derive(Debug)]
 pub struct UpmappingResult<T> {
     /// The macro call site.
diff --git a/src/tools/rust-analyzer/crates/ide/src/rename.rs b/src/tools/rust-analyzer/crates/ide/src/rename.rs
index 42b7472c645..e46cb5a781f 100644
--- a/src/tools/rust-analyzer/crates/ide/src/rename.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/rename.rs
@@ -2976,6 +2976,62 @@ fn test() {
     }
 
     #[test]
+    fn asm_operand() {
+        check(
+            "bose",
+            r#"
+//- minicore: asm
+fn test() {
+    core::arch::asm!(
+        "push {base}",
+        base$0 = const 0
+    );
+}
+"#,
+            r#"
+fn test() {
+    core::arch::asm!(
+        "push {bose}",
+        bose = const 0
+    );
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn asm_operand2() {
+        check(
+            "bose",
+            r#"
+//- minicore: asm
+fn test() {
+    core::arch::asm!(
+        "push {base$0}",
+        "push {base}",
+        boo = const 0,
+        virtual_free = sym VIRTUAL_FREE,
+        base = const 0,
+        boo = const 0,
+    );
+}
+"#,
+            r#"
+fn test() {
+    core::arch::asm!(
+        "push {bose}",
+        "push {bose}",
+        boo = const 0,
+        virtual_free = sym VIRTUAL_FREE,
+        bose = const 0,
+        boo = const 0,
+    );
+}
+"#,
+        );
+    }
+
+    #[test]
     fn rename_path_inside_use_tree() {
         check(
             "Baz",
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/format.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/format.rs
index 518e7145479..7234108701a 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/format.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/format.rs
@@ -19,21 +19,21 @@ pub(super) fn highlight_format_string(
     expanded_string: &ast::String,
     range: TextRange,
 ) {
-    if !is_format_string(expanded_string) {
+    if is_format_string(expanded_string) {
+        // FIXME: Replace this with the HIR info we have now.
+        lex_format_specifiers(string, &mut |piece_range, kind| {
+            if let Some(highlight) = highlight_format_specifier(kind) {
+                stack.add(HlRange {
+                    range: piece_range + range.start(),
+                    highlight: highlight.into(),
+                    binding_hash: None,
+                });
+            }
+        });
+
         return;
     }
 
-    // FIXME: Replace this with the HIR info we have now.
-    lex_format_specifiers(string, &mut |piece_range, kind| {
-        if let Some(highlight) = highlight_format_specifier(kind) {
-            stack.add(HlRange {
-                range: piece_range + range.start(),
-                highlight: highlight.into(),
-                binding_hash: None,
-            });
-        }
-    });
-
     if let Some(parts) = sema.as_format_args_parts(string) {
         parts.into_iter().for_each(|(range, res)| {
             if let Some(res) = res {
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs
index eeba9cf35c9..96375937a12 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs
@@ -534,6 +534,10 @@ pub(super) fn highlight_def(
         Definition::BuiltinAttr(_) => Highlight::new(HlTag::Symbol(SymbolKind::BuiltinAttr)),
         Definition::ToolModule(_) => Highlight::new(HlTag::Symbol(SymbolKind::ToolModule)),
         Definition::DeriveHelper(_) => Highlight::new(HlTag::Symbol(SymbolKind::DeriveHelper)),
+        Definition::InlineAsmRegOrRegClass(_) => {
+            Highlight::new(HlTag::Symbol(SymbolKind::InlineAsmRegOrRegClass))
+        }
+        Definition::InlineAsmOperand(_) => Highlight::new(HlTag::Symbol(SymbolKind::Local)),
     };
 
     let def_crate = def.krate(db);
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs
index bc1ec530076..5583f1bc8df 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs
@@ -315,6 +315,8 @@ fn module_def_to_hl_tag(def: Definition) -> HlTag {
         Definition::BuiltinAttr(_) => SymbolKind::BuiltinAttr,
         Definition::ToolModule(_) => SymbolKind::ToolModule,
         Definition::DeriveHelper(_) => SymbolKind::DeriveHelper,
+        Definition::InlineAsmRegOrRegClass(_) => SymbolKind::InlineAsmRegOrRegClass,
+        Definition::InlineAsmOperand(_) => SymbolKind::Local,
     };
     HlTag::Symbol(symbol)
 }
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs
index e329023606a..3b5d1af0ac7 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs
@@ -146,6 +146,7 @@ impl HlTag {
                 SymbolKind::Field => "field",
                 SymbolKind::Function => "function",
                 SymbolKind::Impl => "self_type",
+                SymbolKind::InlineAsmRegOrRegClass => "reg",
                 SymbolKind::Label => "label",
                 SymbolKind::LifetimeParam => "lifetime",
                 SymbolKind::Local => "variable",
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_asm.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_asm.html
new file mode 100644
index 00000000000..d830a388721
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_asm.html
@@ -0,0 +1,97 @@
+
+<style>
+body                { margin: 0; }
+pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime           { color: #DFAF8F; font-style: italic; }
+.label              { color: #DFAF8F; font-style: italic; }
+.comment            { color: #7F9F7F; }
+.documentation      { color: #629755; }
+.intra_doc_link     { font-style: italic; }
+.injected           { opacity: 0.65 ; }
+.struct, .enum      { color: #7CB8BB; }
+.enum_variant       { color: #BDE0F3; }
+.string_literal     { color: #CC9393; }
+.field              { color: #94BFF3; }
+.function           { color: #93E0E3; }
+.function.unsafe    { color: #BC8383; }
+.trait.unsafe       { color: #BC8383; }
+.operator.unsafe    { color: #BC8383; }
+.mutable.unsafe     { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
+.parameter          { color: #94BFF3; }
+.text               { color: #DCDCCC; }
+.type               { color: #7CB8BB; }
+.builtin_type       { color: #8CD0D3; }
+.type_param         { color: #DFAF8F; }
+.attribute          { color: #94BFF3; }
+.numeric_literal    { color: #BFEBBF; }
+.bool_literal       { color: #BFE6EB; }
+.macro              { color: #94BFF3; }
+.proc_macro         { color: #94BFF3; text-decoration: underline; }
+.derive             { color: #94BFF3; font-style: italic; }
+.module             { color: #AFD8AF; }
+.value_param        { color: #DCDCCC; }
+.variable           { color: #DCDCCC; }
+.format_specifier   { color: #CC696B; }
+.mutable            { text-decoration: underline; }
+.escape_sequence    { color: #94BFF3; }
+.keyword            { color: #F0DFAF; font-weight: bold; }
+.control            { font-style: italic; }
+.reference          { font-style: italic; font-weight: bold; }
+.const              { font-weight: bolder; }
+
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+    <span class="keyword unsafe">unsafe</span> <span class="brace">{</span>
+        <span class="keyword">let</span> <span class="variable declaration">foo</span> <span class="operator">=</span> <span class="numeric_literal">1</span><span class="semicolon">;</span>
+        <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">o</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span>
+        <span class="module crate_root default_library library">core</span><span class="operator">::</span><span class="module default_library library">arch</span><span class="operator">::</span><span class="macro default_library library unsafe">asm</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span>
+            <span class="string_literal macro">"%input = </span><span class="variable">O</span><span class="string_literal macro">pLoad _ {</span><span class="variable">0</span><span class="string_literal macro">}"</span><span class="comma macro">,</span>
+            <span class="macro default_library library macro">concat</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"%result = "</span><span class="comma macro">,</span> <span class="string_literal macro">"bar"</span><span class="comma macro">,</span> <span class="string_literal macro">" _ %input"</span><span class="parenthesis macro">)</span><span class="comma macro">,</span>
+            <span class="string_literal macro">"OpStore {</span><span class="variable">1</span><span class="string_literal macro">} %result</span><span class="variable">"</span><span class="comma macro">,</span>
+            <span class="keyword control macro">in</span><span class="parenthesis macro">(</span><span class="reg library macro">reg</span><span class="parenthesis macro">)</span> <span class="operator macro">&</span><span class="variable macro">foo</span><span class="comma macro">,</span>
+            <span class="keyword control macro">in</span><span class="parenthesis macro">(</span><span class="reg library macro">reg</span><span class="parenthesis macro">)</span> <span class="operator macro">&</span><span class="keyword macro">mut</span> <span class="variable macro mutable">o</span><span class="comma macro">,</span>
+        <span class="parenthesis macro">)</span><span class="semicolon">;</span>
+
+        <span class="keyword">let</span> <span class="variable declaration">thread_id</span><span class="colon">:</span> <span class="builtin_type">usize</span><span class="semicolon">;</span>
+        <span class="module crate_root default_library library">core</span><span class="operator">::</span><span class="module default_library library">arch</span><span class="operator">::</span><span class="macro default_library library unsafe">asm</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"
+            mov {</span><span class="variable">0</span><span class="string_literal macro">}, gs:[0x30]
+            mov {</span><span class="variable">0</span><span class="string_literal macro">}, [{</span><span class="variable">0</span><span class="string_literal macro">}+0x48]
+        "</span><span class="comma macro">,</span> <span class="keyword macro">out</span><span class="parenthesis macro">(</span><span class="reg library macro">reg</span><span class="parenthesis macro">)</span> <span class="variable macro">thread_id</span><span class="comma macro">,</span> <span class="keyword macro">options</span><span class="parenthesis macro">(</span><span class="keyword macro">pure</span><span class="comma macro">,</span> <span class="keyword macro">readonly</span><span class="comma macro">,</span> <span class="keyword macro">nostack</span><span class="parenthesis macro">)</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
+
+        <span class="keyword">static</span> <span class="static declaration">UNMAP_BASE</span><span class="colon">:</span> <span class="builtin_type">usize</span><span class="semicolon">;</span>
+        <span class="keyword const">const</span> <span class="constant const declaration">MEM_RELEASE</span><span class="colon">:</span> <span class="builtin_type">usize</span><span class="semicolon">;</span>
+        <span class="keyword">static</span> <span class="static declaration">VirtualFree</span><span class="colon">:</span> <span class="builtin_type">usize</span><span class="semicolon">;</span>
+        <span class="keyword const">const</span> <span class="constant const declaration">OffPtr</span><span class="colon">:</span> <span class="builtin_type">usize</span><span class="semicolon">;</span>
+        <span class="keyword const">const</span> <span class="constant const declaration">OffFn</span><span class="colon">:</span> <span class="builtin_type">usize</span><span class="semicolon">;</span>
+        <span class="module crate_root default_library library">core</span><span class="operator">::</span><span class="module default_library library">arch</span><span class="operator">::</span><span class="macro default_library library unsafe">asm</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"
+            push {</span><span class="variable">free_type</span><span class="string_literal macro">}
+            push {</span><span class="variable">free_size</span><span class="string_literal macro">}
+            push {</span><span class="variable">base</span><span class="string_literal macro">}
+
+            mov eax, fs:[30h]
+            mov eax, [eax+8h]
+            add eax, {</span><span class="variable">off_fn</span><span class="string_literal macro">}
+            mov [eax-{</span><span class="variable">off_fn</span><span class="string_literal macro">}+{</span><span class="variable">off_ptr</span><span class="string_literal macro">}], eax
+
+            push eax
+
+            jmp {</span><span class="variable">virtual_free</span><span class="string_literal macro">}
+            "</span><span class="comma macro">,</span>
+            <span class="variable declaration macro">off_ptr</span> <span class="operator macro">=</span> <span class="keyword macro">const</span> <span class="constant const macro">OffPtr</span><span class="comma macro">,</span>
+            <span class="variable declaration macro">off_fn</span>  <span class="operator macro">=</span> <span class="keyword macro">const</span> <span class="constant const macro">OffFn</span><span class="comma macro">,</span>
+
+            <span class="variable declaration macro">free_size</span> <span class="operator macro">=</span> <span class="keyword macro">const</span> <span class="numeric_literal macro">0</span><span class="comma macro">,</span>
+            <span class="variable declaration macro">free_type</span> <span class="operator macro">=</span> <span class="keyword macro">const</span> <span class="constant const macro">MEM_RELEASE</span><span class="comma macro">,</span>
+
+            <span class="variable declaration macro">virtual_free</span> <span class="operator macro">=</span> <span class="keyword macro">sym</span> <span class="static macro">VirtualFree</span><span class="comma macro">,</span>
+
+            <span class="variable declaration macro">base</span> <span class="operator macro">=</span> <span class="keyword macro">sym</span> <span class="static macro">UNMAP_BASE</span><span class="comma macro">,</span>
+            <span class="keyword macro">options</span><span class="parenthesis macro">(</span><span class="keyword macro">noreturn</span><span class="parenthesis macro">)</span><span class="comma macro">,</span>
+        <span class="parenthesis macro">)</span><span class="semicolon">;</span>
+    <span class="brace">}</span>
+<span class="brace">}</span></code></pre>
\ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
index cb47fc68bc1..d5b9fc0e2c4 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
@@ -165,16 +165,16 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     <span class="macro">toho</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"{}fmt"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
     <span class="keyword">let</span> <span class="variable declaration">i</span><span class="colon">:</span> <span class="builtin_type">u64</span> <span class="operator">=</span> <span class="numeric_literal">3</span><span class="semicolon">;</span>
     <span class="keyword">let</span> <span class="variable declaration">o</span><span class="colon">:</span> <span class="builtin_type">u64</span><span class="semicolon">;</span>
-    <span class="macro default_library library">asm</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span>
-        <span class="string_literal macro">"mov </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal macro">, </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span>
-        <span class="string_literal macro">"add </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal macro">, 5"</span><span class="comma macro">,</span>
-        <span class="none macro">out</span><span class="parenthesis macro">(</span><span class="none macro">reg</span><span class="parenthesis macro">)</span> <span class="none macro">o</span><span class="comma macro">,</span>
-        <span class="keyword control macro">in</span><span class="parenthesis macro">(</span><span class="none macro">reg</span><span class="parenthesis macro">)</span> <span class="none macro">i</span><span class="comma macro">,</span>
+    <span class="module crate_root default_library library">core</span><span class="operator">::</span><span class="module default_library library">arch</span><span class="operator">::</span><span class="macro default_library library unsafe">asm</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span>
+        <span class="string_literal macro">"mov {</span><span class="variable">0</span><span class="string_literal macro">}, {</span><span class="variable">1</span><span class="string_literal macro">}"</span><span class="comma macro">,</span>
+        <span class="string_literal macro">"add {</span><span class="variable">0</span><span class="string_literal macro">}, 5</span><span class="variable">"</span><span class="comma macro">,</span>
+        <span class="keyword macro">out</span><span class="parenthesis macro">(</span><span class="reg library macro">reg</span><span class="parenthesis macro">)</span> <span class="variable macro">o</span><span class="comma macro">,</span>
+        <span class="keyword control macro">in</span><span class="parenthesis macro">(</span><span class="reg library macro">reg</span><span class="parenthesis macro">)</span> <span class="variable macro">i</span><span class="comma macro">,</span>
     <span class="parenthesis macro">)</span><span class="semicolon">;</span>
 
     <span class="keyword const">const</span> <span class="constant const declaration">CONSTANT</span><span class="colon">:</span> <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="colon">:</span>
     <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">m</span> <span class="operator">=</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
     <span class="macro default_library library">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="macro default_library library macro">concat</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"{}"</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="string_literal macro">"{}"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
-    <span class="macro default_library library">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="variable reference">backslash</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="constant const">CONSTANT</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="variable mutable">m</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="variable macro reference">backslash</span><span class="comma macro">,</span> <span class="macro default_library library macro">format_args</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="unresolved_reference macro">foo</span><span class="comma macro">,</span> <span class="string_literal macro">"bar"</span><span class="comma macro">,</span> <span class="macro macro">toho</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="variable macro reference">backslash</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
-    <span class="macro">reuse_twice</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="variable reference">backslash</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
+    <span class="macro default_library library">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="variable">backslash</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="variable">CONSTANT</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="variable">m</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="variable macro reference">backslash</span><span class="comma macro">,</span> <span class="macro default_library library macro">format_args</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="unresolved_reference macro">foo</span><span class="comma macro">,</span> <span class="string_literal macro">"bar"</span><span class="comma macro">,</span> <span class="macro macro">toho</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="variable macro reference">backslash</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
+    <span class="macro">reuse_twice</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="variable">backslash</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
 <span class="brace">}</span></code></pre>
\ No newline at end of file
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs
index c06ea155fb8..f47b2115bfc 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs
@@ -549,7 +549,7 @@ fn main() {
     toho!("{}fmt", 0);
     let i: u64 = 3;
     let o: u64;
-    asm!(
+    core::arch::asm!(
         "mov {0}, {1}",
         "add {0}, 5",
         out(reg) o,
@@ -1275,3 +1275,64 @@ fn f<'de, T: Deserialize<'de>>() {
     );
     let _ = analysis.highlight(HL_CONFIG, file_id).unwrap();
 }
+
+#[test]
+fn test_asm_highlighting() {
+    check_highlighting(
+        r#"
+//- minicore: asm, concat
+fn main() {
+    unsafe {
+        let foo = 1;
+        let mut o = 0;
+        core::arch::asm!(
+            "%input = OpLoad _ {0}",
+            concat!("%result = ", "bar", " _ %input"),
+            "OpStore {1} %result",
+            in(reg) &foo,
+            in(reg) &mut o,
+        );
+
+        let thread_id: usize;
+        core::arch::asm!("
+            mov {0}, gs:[0x30]
+            mov {0}, [{0}+0x48]
+        ", out(reg) thread_id, options(pure, readonly, nostack));
+
+        static UNMAP_BASE: usize;
+        const MEM_RELEASE: usize;
+        static VirtualFree: usize;
+        const OffPtr: usize;
+        const OffFn: usize;
+        core::arch::asm!("
+            push {free_type}
+            push {free_size}
+            push {base}
+
+            mov eax, fs:[30h]
+            mov eax, [eax+8h]
+            add eax, {off_fn}
+            mov [eax-{off_fn}+{off_ptr}], eax
+
+            push eax
+
+            jmp {virtual_free}
+            ",
+            off_ptr = const OffPtr,
+            off_fn  = const OffFn,
+
+            free_size = const 0,
+            free_type = const MEM_RELEASE,
+
+            virtual_free = sym VirtualFree,
+
+            base = sym UNMAP_BASE,
+            options(noreturn),
+        );
+    }
+}
+"#,
+        expect_file!["./test_data/highlight_asm.html"],
+        false,
+    );
+}
diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs
index a678c1f3a70..39ca26fc508 100644
--- a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs
@@ -245,7 +245,7 @@ fn tuple_expr(p: &mut Parser<'_>) -> CompletedMarker {
 
 // test builtin_expr
 // fn foo() {
-//     builtin#asm(0);
+//     builtin#asm("");
 //     builtin#format_args("", 0, 1, a = 2 + 3, a + b);
 //     builtin#offset_of(Foo, bar.baz.0);
 // }
@@ -297,18 +297,183 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> {
         p.expect(T![')']);
         Some(m.complete(p, FORMAT_ARGS_EXPR))
     } else if p.at_contextual_kw(T![asm]) {
-        p.bump_remap(T![asm]);
-        p.expect(T!['(']);
-        // FIXME: We just put expression here so highlighting kind of keeps working
-        expr(p);
-        p.expect(T![')']);
-        Some(m.complete(p, ASM_EXPR))
+        parse_asm_expr(p, m)
     } else {
         m.abandon(p);
         None
     }
 }
 
+// test asm_expr
+// fn foo() {
+//     builtin#asm(
+//         "mov {tmp}, {x}",
+//         "shl {tmp}, 1",
+//         "shl {x}, 2",
+//         "add {x}, {tmp}",
+//         x = inout(reg) x,
+//         tmp = out(reg) _,
+//     );
+// }
+fn parse_asm_expr(p: &mut Parser<'_>, m: Marker) -> Option<CompletedMarker> {
+    p.bump_remap(T![asm]);
+    p.expect(T!['(']);
+    if expr(p).is_none() {
+        p.err_and_bump("expected asm template");
+    }
+    let mut allow_templates = true;
+    while !p.at(EOF) && !p.at(T![')']) {
+        p.expect(T![,]);
+        // accept trailing commas
+        if p.at(T![')']) {
+            break;
+        }
+
+        let op_n = p.start();
+        // Parse clobber_abi
+        if p.eat_contextual_kw(T![clobber_abi]) {
+            parse_clobber_abi(p);
+            op_n.complete(p, ASM_CLOBBER_ABI);
+            allow_templates = false;
+            continue;
+        }
+
+        // Parse options
+        if p.eat_contextual_kw(T![options]) {
+            parse_options(p);
+            op_n.complete(p, ASM_OPTIONS);
+            allow_templates = false;
+            continue;
+        }
+
+        // Parse operand names
+        if p.at(T![ident]) && p.nth_at(1, T![=]) {
+            name(p);
+            p.bump(T![=]);
+            allow_templates = false;
+            true
+        } else {
+            false
+        };
+
+        let op = p.start();
+        let dir_spec = p.start();
+        if p.eat(T![in]) || p.eat_contextual_kw(T![out]) || p.eat_contextual_kw(T![lateout]) {
+            dir_spec.complete(p, ASM_DIR_SPEC);
+            parse_reg(p);
+            expr(p);
+            op.complete(p, ASM_REG_OPERAND);
+            op_n.complete(p, ASM_OPERAND_NAMED);
+        } else if p.eat_contextual_kw(T![inout]) || p.eat_contextual_kw(T![inlateout]) {
+            dir_spec.complete(p, ASM_DIR_SPEC);
+            parse_reg(p);
+            expr(p);
+            if p.eat(T![=>]) {
+                expr(p);
+            }
+            op.complete(p, ASM_REG_OPERAND);
+            op_n.complete(p, ASM_OPERAND_NAMED);
+        } else if p.eat_contextual_kw(T![label]) {
+            dir_spec.abandon(p);
+            block_expr(p);
+            op.complete(p, ASM_OPERAND_NAMED);
+            op_n.complete(p, ASM_LABEL);
+        } else if p.eat(T![const]) {
+            dir_spec.abandon(p);
+            expr(p);
+            op.complete(p, ASM_CONST);
+            op_n.complete(p, ASM_OPERAND_NAMED);
+        } else if p.eat_contextual_kw(T![sym]) {
+            dir_spec.abandon(p);
+            paths::type_path(p);
+            op.complete(p, ASM_SYM);
+            op_n.complete(p, ASM_OPERAND_NAMED);
+        } else if allow_templates {
+            dir_spec.abandon(p);
+            op.abandon(p);
+            op_n.abandon(p);
+            if expr(p).is_none() {
+                p.err_and_bump("expected asm template");
+            }
+            continue;
+        } else {
+            dir_spec.abandon(p);
+            op.abandon(p);
+            op_n.abandon(p);
+            p.err_and_bump("expected asm operand");
+            if p.at(T!['}']) {
+                break;
+            }
+            continue;
+        };
+        allow_templates = false;
+    }
+    p.expect(T![')']);
+    Some(m.complete(p, ASM_EXPR))
+}
+
+fn parse_options(p: &mut Parser<'_>) {
+    p.expect(T!['(']);
+
+    while !p.eat(T![')']) && !p.at(EOF) {
+        const OPTIONS: &[SyntaxKind] = &[
+            T![pure],
+            T![nomem],
+            T![readonly],
+            T![preserves_flags],
+            T![noreturn],
+            T![nostack],
+            T![may_unwind],
+            T![att_syntax],
+            T![raw],
+        ];
+        let m = p.start();
+        if !OPTIONS.iter().any(|&syntax| p.eat_contextual_kw(syntax)) {
+            p.err_and_bump("expected asm option");
+            continue;
+        }
+        m.complete(p, ASM_OPTION);
+
+        // Allow trailing commas
+        if p.eat(T![')']) {
+            break;
+        }
+        p.expect(T![,]);
+    }
+}
+
+fn parse_clobber_abi(p: &mut Parser<'_>) {
+    p.expect(T!['(']);
+
+    while !p.eat(T![')']) && !p.at(EOF) {
+        if !p.expect(T![string]) {
+            break;
+        }
+
+        // Allow trailing commas
+        if p.eat(T![')']) {
+            break;
+        }
+        p.expect(T![,]);
+    }
+}
+
+fn parse_reg(p: &mut Parser<'_>) {
+    p.expect(T!['(']);
+    if p.at(T![ident]) {
+        let m = p.start();
+        name_ref(p);
+        m.complete(p, ASM_REG_SPEC);
+    } else if p.at(T![string]) {
+        let m = p.start();
+        p.bump_any();
+        m.complete(p, ASM_REG_SPEC);
+    } else {
+        p.err_and_bump("expected register name");
+    }
+    p.expect(T![')']);
+}
+
 // test array_expr
 // fn foo() {
 //     [];
diff --git a/src/tools/rust-analyzer/crates/parser/src/parser.rs b/src/tools/rust-analyzer/crates/parser/src/parser.rs
index 7d3eb5de25f..f6b3783d1ca 100644
--- a/src/tools/rust-analyzer/crates/parser/src/parser.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/parser.rs
@@ -131,6 +131,14 @@ impl<'t> Parser<'t> {
         true
     }
 
+    pub(crate) fn eat_contextual_kw(&mut self, kind: SyntaxKind) -> bool {
+        if !self.at_contextual_kw(kind) {
+            return false;
+        }
+        self.bump_remap(kind);
+        true
+    }
+
     fn at_composite2(&self, n: usize, k1: SyntaxKind, k2: SyntaxKind) -> bool {
         self.inp.kind(self.pos + n) == k1
             && self.inp.kind(self.pos + n + 1) == k2
diff --git a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs
index 00f212487ae..288a07ef44d 100644
--- a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs
+++ b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs
@@ -111,16 +111,32 @@ pub enum SyntaxKind {
     YIELD_KW,
     ASM_KW,
     ASYNC_KW,
+    ATT_SYNTAX_KW,
     AUTO_KW,
     AWAIT_KW,
     BUILTIN_KW,
+    CLOBBER_ABI_KW,
     DEFAULT_KW,
     DYN_KW,
     FORMAT_ARGS_KW,
     GEN_KW,
+    INLATEOUT_KW,
+    INOUT_KW,
+    LABEL_KW,
+    LATEOUT_KW,
     MACRO_RULES_KW,
+    MAY_UNWIND_KW,
+    NOMEM_KW,
+    NORETURN_KW,
+    NOSTACK_KW,
     OFFSET_OF_KW,
+    OPTIONS_KW,
+    OUT_KW,
+    PRESERVES_FLAGS_KW,
+    PURE_KW,
     RAW_KW,
+    READONLY_KW,
+    SYM_KW,
     TRY_KW,
     UNION_KW,
     YEET_KW,
@@ -146,7 +162,20 @@ pub enum SyntaxKind {
     ARG_LIST,
     ARRAY_EXPR,
     ARRAY_TYPE,
+    ASM_CLOBBER_ABI,
+    ASM_CONST,
+    ASM_DIR_SPEC,
     ASM_EXPR,
+    ASM_LABEL,
+    ASM_OPERAND,
+    ASM_OPERAND_EXPR,
+    ASM_OPERAND_NAMED,
+    ASM_OPTION,
+    ASM_OPTIONS,
+    ASM_PIECE,
+    ASM_REG_OPERAND,
+    ASM_REG_SPEC,
+    ASM_SYM,
     ASSOC_ITEM,
     ASSOC_ITEM_LIST,
     ASSOC_TYPE_ARG,
@@ -364,14 +393,30 @@ impl SyntaxKind {
     pub fn is_contextual_keyword(self, edition: Edition) -> bool {
         match self {
             ASM_KW => true,
+            ATT_SYNTAX_KW => true,
             AUTO_KW => true,
             BUILTIN_KW => true,
+            CLOBBER_ABI_KW => true,
             DEFAULT_KW => true,
             DYN_KW if edition < Edition::Edition2018 => true,
             FORMAT_ARGS_KW => true,
+            INLATEOUT_KW => true,
+            INOUT_KW => true,
+            LABEL_KW => true,
+            LATEOUT_KW => true,
             MACRO_RULES_KW => true,
+            MAY_UNWIND_KW => true,
+            NOMEM_KW => true,
+            NORETURN_KW => true,
+            NOSTACK_KW => true,
             OFFSET_OF_KW => true,
+            OPTIONS_KW => true,
+            OUT_KW => true,
+            PRESERVES_FLAGS_KW => true,
+            PURE_KW => true,
             RAW_KW => true,
+            READONLY_KW => true,
+            SYM_KW => true,
             UNION_KW => true,
             YEET_KW => true,
             _ => false,
@@ -435,14 +480,30 @@ impl SyntaxKind {
             GEN_KW if Edition::Edition2024 <= edition => true,
             TRY_KW if Edition::Edition2018 <= edition => true,
             ASM_KW => true,
+            ATT_SYNTAX_KW => true,
             AUTO_KW => true,
             BUILTIN_KW => true,
+            CLOBBER_ABI_KW => true,
             DEFAULT_KW => true,
             DYN_KW if edition < Edition::Edition2018 => true,
             FORMAT_ARGS_KW => true,
+            INLATEOUT_KW => true,
+            INOUT_KW => true,
+            LABEL_KW => true,
+            LATEOUT_KW => true,
             MACRO_RULES_KW => true,
+            MAY_UNWIND_KW => true,
+            NOMEM_KW => true,
+            NORETURN_KW => true,
+            NOSTACK_KW => true,
             OFFSET_OF_KW => true,
+            OPTIONS_KW => true,
+            OUT_KW => true,
+            PRESERVES_FLAGS_KW => true,
+            PURE_KW => true,
             RAW_KW => true,
+            READONLY_KW => true,
+            SYM_KW => true,
             UNION_KW => true,
             YEET_KW => true,
             _ => false,
@@ -580,14 +641,30 @@ impl SyntaxKind {
     pub fn from_contextual_keyword(ident: &str, edition: Edition) -> Option<SyntaxKind> {
         let kw = match ident {
             "asm" => ASM_KW,
+            "att_syntax" => ATT_SYNTAX_KW,
             "auto" => AUTO_KW,
             "builtin" => BUILTIN_KW,
+            "clobber_abi" => CLOBBER_ABI_KW,
             "default" => DEFAULT_KW,
             "dyn" if edition < Edition::Edition2018 => DYN_KW,
             "format_args" => FORMAT_ARGS_KW,
+            "inlateout" => INLATEOUT_KW,
+            "inout" => INOUT_KW,
+            "label" => LABEL_KW,
+            "lateout" => LATEOUT_KW,
             "macro_rules" => MACRO_RULES_KW,
+            "may_unwind" => MAY_UNWIND_KW,
+            "nomem" => NOMEM_KW,
+            "noreturn" => NORETURN_KW,
+            "nostack" => NOSTACK_KW,
             "offset_of" => OFFSET_OF_KW,
+            "options" => OPTIONS_KW,
+            "out" => OUT_KW,
+            "preserves_flags" => PRESERVES_FLAGS_KW,
+            "pure" => PURE_KW,
             "raw" => RAW_KW,
+            "readonly" => READONLY_KW,
+            "sym" => SYM_KW,
             "union" => UNION_KW,
             "yeet" => YEET_KW,
             _ => return None,
@@ -630,4 +707,4 @@ impl SyntaxKind {
     }
 }
 #[macro_export]
-macro_rules ! T { [$] => { $ crate :: SyntaxKind :: DOLLAR } ; [;] => { $ crate :: SyntaxKind :: SEMICOLON } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: SyntaxKind :: R_CURLY } ; ['['] => { $ crate :: SyntaxKind :: L_BRACK } ; [']'] => { $ crate :: SyntaxKind :: R_BRACK } ; [<] => { $ crate :: SyntaxKind :: L_ANGLE } ; [>] => { $ crate :: SyntaxKind :: R_ANGLE } ; [@] => { $ crate :: SyntaxKind :: AT } ; [#] => { $ crate :: SyntaxKind :: POUND } ; [~] => { $ crate :: SyntaxKind :: TILDE } ; [?] => { $ crate :: SyntaxKind :: QUESTION } ; [&] => { $ crate :: SyntaxKind :: AMP } ; [|] => { $ crate :: SyntaxKind :: PIPE } ; [+] => { $ crate :: SyntaxKind :: PLUS } ; [*] => { $ crate :: SyntaxKind :: STAR } ; [/] => { $ crate :: SyntaxKind :: SLASH } ; [^] => { $ crate :: SyntaxKind :: CARET } ; [%] => { $ crate :: SyntaxKind :: PERCENT } ; [_] => { $ crate :: SyntaxKind :: UNDERSCORE } ; [.] => { $ crate :: SyntaxKind :: DOT } ; [..] => { $ crate :: SyntaxKind :: DOT2 } ; [...] => { $ crate :: SyntaxKind :: DOT3 } ; [..=] => { $ crate :: SyntaxKind :: DOT2EQ } ; [:] => { $ crate :: SyntaxKind :: COLON } ; [::] => { $ crate :: SyntaxKind :: COLON2 } ; [=] => { $ crate :: SyntaxKind :: EQ } ; [==] => { $ crate :: SyntaxKind :: EQ2 } ; [=>] => { $ crate :: SyntaxKind :: FAT_ARROW } ; [!] => { $ crate :: SyntaxKind :: BANG } ; [!=] => { $ crate :: SyntaxKind :: NEQ } ; [-] => { $ crate :: SyntaxKind :: MINUS } ; [->] => { $ crate :: SyntaxKind :: THIN_ARROW } ; [<=] => { $ crate :: SyntaxKind :: LTEQ } ; [>=] => { $ crate :: SyntaxKind :: GTEQ } ; [+=] => { $ crate :: SyntaxKind :: PLUSEQ } ; [-=] => { $ crate :: SyntaxKind :: MINUSEQ } ; [|=] => { $ crate :: SyntaxKind :: PIPEEQ } ; [&=] => { $ crate :: SyntaxKind :: AMPEQ } ; [^=] => { $ crate :: SyntaxKind :: CARETEQ } ; [/=] => { $ crate :: SyntaxKind :: SLASHEQ } ; [*=] => { $ crate :: SyntaxKind :: STAREQ } ; [%=] => { $ crate :: SyntaxKind :: PERCENTEQ } ; [&&] => { $ crate :: SyntaxKind :: AMP2 } ; [||] => { $ crate :: SyntaxKind :: PIPE2 } ; [<<] => { $ crate :: SyntaxKind :: SHL } ; [>>] => { $ crate :: SyntaxKind :: SHR } ; [<<=] => { $ crate :: SyntaxKind :: SHLEQ } ; [>>=] => { $ crate :: SyntaxKind :: SHREQ } ; [Self] => { $ crate :: SyntaxKind :: SELF_TYPE_KW } ; [abstract] => { $ crate :: SyntaxKind :: ABSTRACT_KW } ; [as] => { $ crate :: SyntaxKind :: AS_KW } ; [become] => { $ crate :: SyntaxKind :: BECOME_KW } ; [box] => { $ crate :: SyntaxKind :: BOX_KW } ; [break] => { $ crate :: SyntaxKind :: BREAK_KW } ; [const] => { $ crate :: SyntaxKind :: CONST_KW } ; [continue] => { $ crate :: SyntaxKind :: CONTINUE_KW } ; [crate] => { $ crate :: SyntaxKind :: CRATE_KW } ; [do] => { $ crate :: SyntaxKind :: DO_KW } ; [else] => { $ crate :: SyntaxKind :: ELSE_KW } ; [enum] => { $ crate :: SyntaxKind :: ENUM_KW } ; [extern] => { $ crate :: SyntaxKind :: EXTERN_KW } ; [false] => { $ crate :: SyntaxKind :: FALSE_KW } ; [final] => { $ crate :: SyntaxKind :: FINAL_KW } ; [fn] => { $ crate :: SyntaxKind :: FN_KW } ; [for] => { $ crate :: SyntaxKind :: FOR_KW } ; [if] => { $ crate :: SyntaxKind :: IF_KW } ; [impl] => { $ crate :: SyntaxKind :: IMPL_KW } ; [in] => { $ crate :: SyntaxKind :: IN_KW } ; [let] => { $ crate :: SyntaxKind :: LET_KW } ; [loop] => { $ crate :: SyntaxKind :: LOOP_KW } ; [macro] => { $ crate :: SyntaxKind :: MACRO_KW } ; [match] => { $ crate :: SyntaxKind :: MATCH_KW } ; [mod] => { $ crate :: SyntaxKind :: MOD_KW } ; [move] => { $ crate :: SyntaxKind :: MOVE_KW } ; [mut] => { $ crate :: SyntaxKind :: MUT_KW } ; [override] => { $ crate :: SyntaxKind :: OVERRIDE_KW } ; [priv] => { $ crate :: SyntaxKind :: PRIV_KW } ; [pub] => { $ crate :: SyntaxKind :: PUB_KW } ; [ref] => { $ crate :: SyntaxKind :: REF_KW } ; [return] => { $ crate :: SyntaxKind :: RETURN_KW } ; [self] => { $ crate :: SyntaxKind :: SELF_KW } ; [static] => { $ crate :: SyntaxKind :: STATIC_KW } ; [struct] => { $ crate :: SyntaxKind :: STRUCT_KW } ; [super] => { $ crate :: SyntaxKind :: SUPER_KW } ; [trait] => { $ crate :: SyntaxKind :: TRAIT_KW } ; [true] => { $ crate :: SyntaxKind :: TRUE_KW } ; [type] => { $ crate :: SyntaxKind :: TYPE_KW } ; [typeof] => { $ crate :: SyntaxKind :: TYPEOF_KW } ; [unsafe] => { $ crate :: SyntaxKind :: UNSAFE_KW } ; [unsized] => { $ crate :: SyntaxKind :: UNSIZED_KW } ; [use] => { $ crate :: SyntaxKind :: USE_KW } ; [virtual] => { $ crate :: SyntaxKind :: VIRTUAL_KW } ; [where] => { $ crate :: SyntaxKind :: WHERE_KW } ; [while] => { $ crate :: SyntaxKind :: WHILE_KW } ; [yield] => { $ crate :: SyntaxKind :: YIELD_KW } ; [asm] => { $ crate :: SyntaxKind :: ASM_KW } ; [auto] => { $ crate :: SyntaxKind :: AUTO_KW } ; [builtin] => { $ crate :: SyntaxKind :: BUILTIN_KW } ; [default] => { $ crate :: SyntaxKind :: DEFAULT_KW } ; [dyn] => { $ crate :: SyntaxKind :: DYN_KW } ; [format_args] => { $ crate :: SyntaxKind :: FORMAT_ARGS_KW } ; [macro_rules] => { $ crate :: SyntaxKind :: MACRO_RULES_KW } ; [offset_of] => { $ crate :: SyntaxKind :: OFFSET_OF_KW } ; [raw] => { $ crate :: SyntaxKind :: RAW_KW } ; [union] => { $ crate :: SyntaxKind :: UNION_KW } ; [yeet] => { $ crate :: SyntaxKind :: YEET_KW } ; [async] => { $ crate :: SyntaxKind :: ASYNC_KW } ; [await] => { $ crate :: SyntaxKind :: AWAIT_KW } ; [dyn] => { $ crate :: SyntaxKind :: DYN_KW } ; [gen] => { $ crate :: SyntaxKind :: GEN_KW } ; [try] => { $ crate :: SyntaxKind :: TRY_KW } ; [lifetime_ident] => { $ crate :: SyntaxKind :: LIFETIME_IDENT } ; [int_number] => { $ crate :: SyntaxKind :: INT_NUMBER } ; [ident] => { $ crate :: SyntaxKind :: IDENT } ; [string] => { $ crate :: SyntaxKind :: STRING } ; [shebang] => { $ crate :: SyntaxKind :: SHEBANG } ; }
+macro_rules ! T { [$] => { $ crate :: SyntaxKind :: DOLLAR } ; [;] => { $ crate :: SyntaxKind :: SEMICOLON } ; [,] => { $ crate :: SyntaxKind :: COMMA } ; ['('] => { $ crate :: SyntaxKind :: L_PAREN } ; [')'] => { $ crate :: SyntaxKind :: R_PAREN } ; ['{'] => { $ crate :: SyntaxKind :: L_CURLY } ; ['}'] => { $ crate :: SyntaxKind :: R_CURLY } ; ['['] => { $ crate :: SyntaxKind :: L_BRACK } ; [']'] => { $ crate :: SyntaxKind :: R_BRACK } ; [<] => { $ crate :: SyntaxKind :: L_ANGLE } ; [>] => { $ crate :: SyntaxKind :: R_ANGLE } ; [@] => { $ crate :: SyntaxKind :: AT } ; [#] => { $ crate :: SyntaxKind :: POUND } ; [~] => { $ crate :: SyntaxKind :: TILDE } ; [?] => { $ crate :: SyntaxKind :: QUESTION } ; [&] => { $ crate :: SyntaxKind :: AMP } ; [|] => { $ crate :: SyntaxKind :: PIPE } ; [+] => { $ crate :: SyntaxKind :: PLUS } ; [*] => { $ crate :: SyntaxKind :: STAR } ; [/] => { $ crate :: SyntaxKind :: SLASH } ; [^] => { $ crate :: SyntaxKind :: CARET } ; [%] => { $ crate :: SyntaxKind :: PERCENT } ; [_] => { $ crate :: SyntaxKind :: UNDERSCORE } ; [.] => { $ crate :: SyntaxKind :: DOT } ; [..] => { $ crate :: SyntaxKind :: DOT2 } ; [...] => { $ crate :: SyntaxKind :: DOT3 } ; [..=] => { $ crate :: SyntaxKind :: DOT2EQ } ; [:] => { $ crate :: SyntaxKind :: COLON } ; [::] => { $ crate :: SyntaxKind :: COLON2 } ; [=] => { $ crate :: SyntaxKind :: EQ } ; [==] => { $ crate :: SyntaxKind :: EQ2 } ; [=>] => { $ crate :: SyntaxKind :: FAT_ARROW } ; [!] => { $ crate :: SyntaxKind :: BANG } ; [!=] => { $ crate :: SyntaxKind :: NEQ } ; [-] => { $ crate :: SyntaxKind :: MINUS } ; [->] => { $ crate :: SyntaxKind :: THIN_ARROW } ; [<=] => { $ crate :: SyntaxKind :: LTEQ } ; [>=] => { $ crate :: SyntaxKind :: GTEQ } ; [+=] => { $ crate :: SyntaxKind :: PLUSEQ } ; [-=] => { $ crate :: SyntaxKind :: MINUSEQ } ; [|=] => { $ crate :: SyntaxKind :: PIPEEQ } ; [&=] => { $ crate :: SyntaxKind :: AMPEQ } ; [^=] => { $ crate :: SyntaxKind :: CARETEQ } ; [/=] => { $ crate :: SyntaxKind :: SLASHEQ } ; [*=] => { $ crate :: SyntaxKind :: STAREQ } ; [%=] => { $ crate :: SyntaxKind :: PERCENTEQ } ; [&&] => { $ crate :: SyntaxKind :: AMP2 } ; [||] => { $ crate :: SyntaxKind :: PIPE2 } ; [<<] => { $ crate :: SyntaxKind :: SHL } ; [>>] => { $ crate :: SyntaxKind :: SHR } ; [<<=] => { $ crate :: SyntaxKind :: SHLEQ } ; [>>=] => { $ crate :: SyntaxKind :: SHREQ } ; [Self] => { $ crate :: SyntaxKind :: SELF_TYPE_KW } ; [abstract] => { $ crate :: SyntaxKind :: ABSTRACT_KW } ; [as] => { $ crate :: SyntaxKind :: AS_KW } ; [become] => { $ crate :: SyntaxKind :: BECOME_KW } ; [box] => { $ crate :: SyntaxKind :: BOX_KW } ; [break] => { $ crate :: SyntaxKind :: BREAK_KW } ; [const] => { $ crate :: SyntaxKind :: CONST_KW } ; [continue] => { $ crate :: SyntaxKind :: CONTINUE_KW } ; [crate] => { $ crate :: SyntaxKind :: CRATE_KW } ; [do] => { $ crate :: SyntaxKind :: DO_KW } ; [else] => { $ crate :: SyntaxKind :: ELSE_KW } ; [enum] => { $ crate :: SyntaxKind :: ENUM_KW } ; [extern] => { $ crate :: SyntaxKind :: EXTERN_KW } ; [false] => { $ crate :: SyntaxKind :: FALSE_KW } ; [final] => { $ crate :: SyntaxKind :: FINAL_KW } ; [fn] => { $ crate :: SyntaxKind :: FN_KW } ; [for] => { $ crate :: SyntaxKind :: FOR_KW } ; [if] => { $ crate :: SyntaxKind :: IF_KW } ; [impl] => { $ crate :: SyntaxKind :: IMPL_KW } ; [in] => { $ crate :: SyntaxKind :: IN_KW } ; [let] => { $ crate :: SyntaxKind :: LET_KW } ; [loop] => { $ crate :: SyntaxKind :: LOOP_KW } ; [macro] => { $ crate :: SyntaxKind :: MACRO_KW } ; [match] => { $ crate :: SyntaxKind :: MATCH_KW } ; [mod] => { $ crate :: SyntaxKind :: MOD_KW } ; [move] => { $ crate :: SyntaxKind :: MOVE_KW } ; [mut] => { $ crate :: SyntaxKind :: MUT_KW } ; [override] => { $ crate :: SyntaxKind :: OVERRIDE_KW } ; [priv] => { $ crate :: SyntaxKind :: PRIV_KW } ; [pub] => { $ crate :: SyntaxKind :: PUB_KW } ; [ref] => { $ crate :: SyntaxKind :: REF_KW } ; [return] => { $ crate :: SyntaxKind :: RETURN_KW } ; [self] => { $ crate :: SyntaxKind :: SELF_KW } ; [static] => { $ crate :: SyntaxKind :: STATIC_KW } ; [struct] => { $ crate :: SyntaxKind :: STRUCT_KW } ; [super] => { $ crate :: SyntaxKind :: SUPER_KW } ; [trait] => { $ crate :: SyntaxKind :: TRAIT_KW } ; [true] => { $ crate :: SyntaxKind :: TRUE_KW } ; [type] => { $ crate :: SyntaxKind :: TYPE_KW } ; [typeof] => { $ crate :: SyntaxKind :: TYPEOF_KW } ; [unsafe] => { $ crate :: SyntaxKind :: UNSAFE_KW } ; [unsized] => { $ crate :: SyntaxKind :: UNSIZED_KW } ; [use] => { $ crate :: SyntaxKind :: USE_KW } ; [virtual] => { $ crate :: SyntaxKind :: VIRTUAL_KW } ; [where] => { $ crate :: SyntaxKind :: WHERE_KW } ; [while] => { $ crate :: SyntaxKind :: WHILE_KW } ; [yield] => { $ crate :: SyntaxKind :: YIELD_KW } ; [asm] => { $ crate :: SyntaxKind :: ASM_KW } ; [att_syntax] => { $ crate :: SyntaxKind :: ATT_SYNTAX_KW } ; [auto] => { $ crate :: SyntaxKind :: AUTO_KW } ; [builtin] => { $ crate :: SyntaxKind :: BUILTIN_KW } ; [clobber_abi] => { $ crate :: SyntaxKind :: CLOBBER_ABI_KW } ; [default] => { $ crate :: SyntaxKind :: DEFAULT_KW } ; [dyn] => { $ crate :: SyntaxKind :: DYN_KW } ; [format_args] => { $ crate :: SyntaxKind :: FORMAT_ARGS_KW } ; [inlateout] => { $ crate :: SyntaxKind :: INLATEOUT_KW } ; [inout] => { $ crate :: SyntaxKind :: INOUT_KW } ; [label] => { $ crate :: SyntaxKind :: LABEL_KW } ; [lateout] => { $ crate :: SyntaxKind :: LATEOUT_KW } ; [macro_rules] => { $ crate :: SyntaxKind :: MACRO_RULES_KW } ; [may_unwind] => { $ crate :: SyntaxKind :: MAY_UNWIND_KW } ; [nomem] => { $ crate :: SyntaxKind :: NOMEM_KW } ; [noreturn] => { $ crate :: SyntaxKind :: NORETURN_KW } ; [nostack] => { $ crate :: SyntaxKind :: NOSTACK_KW } ; [offset_of] => { $ crate :: SyntaxKind :: OFFSET_OF_KW } ; [options] => { $ crate :: SyntaxKind :: OPTIONS_KW } ; [out] => { $ crate :: SyntaxKind :: OUT_KW } ; [preserves_flags] => { $ crate :: SyntaxKind :: PRESERVES_FLAGS_KW } ; [pure] => { $ crate :: SyntaxKind :: PURE_KW } ; [raw] => { $ crate :: SyntaxKind :: RAW_KW } ; [readonly] => { $ crate :: SyntaxKind :: READONLY_KW } ; [sym] => { $ crate :: SyntaxKind :: SYM_KW } ; [union] => { $ crate :: SyntaxKind :: UNION_KW } ; [yeet] => { $ crate :: SyntaxKind :: YEET_KW } ; [async] => { $ crate :: SyntaxKind :: ASYNC_KW } ; [await] => { $ crate :: SyntaxKind :: AWAIT_KW } ; [dyn] => { $ crate :: SyntaxKind :: DYN_KW } ; [gen] => { $ crate :: SyntaxKind :: GEN_KW } ; [try] => { $ crate :: SyntaxKind :: TRY_KW } ; [lifetime_ident] => { $ crate :: SyntaxKind :: LIFETIME_IDENT } ; [int_number] => { $ crate :: SyntaxKind :: INT_NUMBER } ; [ident] => { $ crate :: SyntaxKind :: IDENT } ; [string] => { $ crate :: SyntaxKind :: STRING } ; [shebang] => { $ crate :: SyntaxKind :: SHEBANG } ; }
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs b/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs
index 9ce5a2ae748..164d0f36f1b 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs
+++ b/src/tools/rust-analyzer/crates/parser/test_data/generated/runner.rs
@@ -19,6 +19,8 @@ mod ok {
     #[test]
     fn as_precedence() { run_and_expect_no_errors("test_data/parser/inline/ok/as_precedence.rs"); }
     #[test]
+    fn asm_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/asm_expr.rs"); }
+    #[test]
     fn assoc_const_eq() {
         run_and_expect_no_errors("test_data/parser/inline/ok/assoc_const_eq.rs");
     }
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/asm_expr.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/asm_expr.rast
new file mode 100644
index 00000000000..4afa9daf590
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/asm_expr.rast
@@ -0,0 +1,83 @@
+SOURCE_FILE
+  FN
+    FN_KW "fn"
+    WHITESPACE " "
+    NAME
+      IDENT "foo"
+    PARAM_LIST
+      L_PAREN "("
+      R_PAREN ")"
+    WHITESPACE " "
+    BLOCK_EXPR
+      STMT_LIST
+        L_CURLY "{"
+        WHITESPACE "\n    "
+        EXPR_STMT
+          ASM_EXPR
+            BUILTIN_KW "builtin"
+            POUND "#"
+            ASM_KW "asm"
+            L_PAREN "("
+            WHITESPACE "\n        "
+            LITERAL
+              STRING "\"mov {tmp}, {x}\""
+            COMMA ","
+            WHITESPACE "\n        "
+            LITERAL
+              STRING "\"shl {tmp}, 1\""
+            COMMA ","
+            WHITESPACE "\n        "
+            LITERAL
+              STRING "\"shl {x}, 2\""
+            COMMA ","
+            WHITESPACE "\n        "
+            LITERAL
+              STRING "\"add {x}, {tmp}\""
+            COMMA ","
+            WHITESPACE "\n        "
+            ASM_OPERAND_NAMED
+              NAME
+                IDENT "x"
+              WHITESPACE " "
+              EQ "="
+              WHITESPACE " "
+              ASM_REG_OPERAND
+                ASM_DIR_SPEC
+                  INOUT_KW "inout"
+                L_PAREN "("
+                ASM_REG_SPEC
+                  NAME_REF
+                    IDENT "reg"
+                R_PAREN ")"
+                WHITESPACE " "
+                PATH_EXPR
+                  PATH
+                    PATH_SEGMENT
+                      NAME_REF
+                        IDENT "x"
+            COMMA ","
+            WHITESPACE "\n        "
+            ASM_OPERAND_NAMED
+              NAME
+                IDENT "tmp"
+              WHITESPACE " "
+              EQ "="
+              WHITESPACE " "
+              ASM_REG_OPERAND
+                ASM_DIR_SPEC
+                  OUT_KW "out"
+                L_PAREN "("
+                ASM_REG_SPEC
+                  NAME_REF
+                    IDENT "reg"
+                R_PAREN ")"
+                WHITESPACE " "
+                UNDERSCORE_EXPR
+                  UNDERSCORE "_"
+            COMMA ","
+            WHITESPACE "\n    "
+            R_PAREN ")"
+          SEMICOLON ";"
+        WHITESPACE "\n"
+        R_CURLY "}"
+  WHITESPACE "\n"
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/asm_expr.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/asm_expr.rs
new file mode 100644
index 00000000000..0906cd3e719
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/asm_expr.rs
@@ -0,0 +1,10 @@
+fn foo() {
+    builtin#asm(
+        "mov {tmp}, {x}",
+        "shl {tmp}, 1",
+        "shl {x}, 2",
+        "add {x}, {tmp}",
+        x = inout(reg) x,
+        tmp = out(reg) _,
+    );
+}
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/builtin_expr.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/builtin_expr.rast
index 361900b6d3e..19a84ac5409 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/builtin_expr.rast
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/builtin_expr.rast
@@ -19,7 +19,7 @@ SOURCE_FILE
             ASM_KW "asm"
             L_PAREN "("
             LITERAL
-              INT_NUMBER "0"
+              STRING "\"\""
             R_PAREN ")"
           SEMICOLON ";"
         WHITESPACE "\n    "
diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/builtin_expr.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/builtin_expr.rs
index 14431b0210e..920d0f794f2 100644
--- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/builtin_expr.rs
+++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/builtin_expr.rs
@@ -1,5 +1,5 @@
 fn foo() {
-    builtin#asm(0);
+    builtin#asm("");
     builtin#format_args("", 0, 1, a = 2 + 3, a + b);
     builtin#offset_of(Foo, bar.baz.0);
 }
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs
index b29268f133f..4902c9f88c1 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs
@@ -80,6 +80,7 @@ pub(crate) fn symbol_kind(symbol_kind: SymbolKind) -> lsp_types::SymbolKind {
         | SymbolKind::ValueParam
         | SymbolKind::Label => lsp_types::SymbolKind::VARIABLE,
         SymbolKind::Union => lsp_types::SymbolKind::STRUCT,
+        SymbolKind::InlineAsmRegOrRegClass => lsp_types::SymbolKind::VARIABLE,
     }
 }
 
@@ -159,6 +160,7 @@ pub(crate) fn completion_item_kind(
             SymbolKind::Variant => lsp_types::CompletionItemKind::ENUM_MEMBER,
             SymbolKind::BuiltinAttr => lsp_types::CompletionItemKind::FUNCTION,
             SymbolKind::ToolModule => lsp_types::CompletionItemKind::MODULE,
+            SymbolKind::InlineAsmRegOrRegClass => lsp_types::CompletionItemKind::KEYWORD,
         },
     }
 }
@@ -702,6 +704,7 @@ fn semantic_token_type_and_modifiers(
             SymbolKind::ProcMacro => types::PROC_MACRO,
             SymbolKind::BuiltinAttr => types::BUILTIN_ATTRIBUTE,
             SymbolKind::ToolModule => types::TOOL_MODULE,
+            SymbolKind::InlineAsmRegOrRegClass => types::KEYWORD,
         },
         HlTag::AttributeBracket => types::ATTRIBUTE_BRACKET,
         HlTag::BoolLiteral => types::BOOLEAN,
diff --git a/src/tools/rust-analyzer/crates/syntax/rust.ungram b/src/tools/rust-analyzer/crates/syntax/rust.ungram
index 43375ce6ae0..52ad439e4de 100644
--- a/src/tools/rust-analyzer/crates/syntax/rust.ungram
+++ b/src/tools/rust-analyzer/crates/syntax/rust.ungram
@@ -391,8 +391,33 @@ Expr =
 OffsetOfExpr =
   Attr* 'builtin' '#' 'offset_of' '(' Type ',' fields:(NameRef ('.' NameRef)* ) ')'
 
+// asm := "asm!(" format_string *("," format_string) *("," operand) [","] ")"
+// global_asm := "global_asm!(" format_string *("," format_string) *("," operand) [","] ")"
+// format_string := STRING_LITERAL / RAW_STRING_LITERAL
 AsmExpr =
-  Attr* 'builtin' '#' 'asm' '(' Expr ')'
+  Attr* 'builtin' '#' 'asm' '(' template:(Expr (',' Expr)*) (AsmPiece (',' AsmPiece)*)? ','? ')'
+
+// operand_expr := expr / "_" / expr "=>" expr / expr "=>" "_"
+AsmOperandExpr = in_expr:Expr ('=>' out_expr:Expr)?
+// dir_spec := "in" / "out" / "lateout" / "inout" / "inlateout"
+AsmDirSpec = 'in' | 'out' | 'lateout' | 'inout' | 'inlateout'
+// reg_spec := <register class> / "\"" <explicit register> "\""
+AsmRegSpec = '@string' | NameRef
+// reg_operand := [ident "="] dir_spec "(" reg_spec ")" operand_expr
+AsmRegOperand = AsmDirSpec '(' AsmRegSpec ')' AsmOperandExpr
+// clobber_abi := "clobber_abi(" <abi> *("," <abi>) [","] ")"
+AsmClobberAbi = 'clobber_abi' '(' ('@string' (',' '@string')* ','?) ')'
+// option := "pure" / "nomem" / "readonly" / "preserves_flags" / "noreturn" / "nostack" / "att_syntax" / "raw"
+AsmOption = 'pure' | 'nomem' | 'readonly' | 'preserves_flags' | 'noreturn' | 'nostack' | 'att_syntax' | 'raw' | 'may_unwind'
+// options := "options(" option *("," option) [","] ")"
+AsmOptions = 'options' '(' AsmOption *(',' AsmOption) ','? ')'
+AsmLabel = 'label' BlockExpr
+AsmSym = 'sym' Path
+AsmConst = 'const' Expr
+// operand := reg_operand / clobber_abi / options
+AsmOperand = AsmRegOperand | AsmLabel | AsmSym | AsmConst
+AsmOperandNamed = (Name '=')? AsmOperand
+AsmPiece = AsmOperandNamed | AsmClobberAbi | AsmOptions
 
 FormatArgsExpr =
   Attr* 'builtin' '#' 'format_args' '('
diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs
index c9b39e9922c..c81a19f3bda 100644
--- a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs
+++ b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs
@@ -65,13 +65,62 @@ impl ArrayType {
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct AsmClobberAbi {
+    pub(crate) syntax: SyntaxNode,
+}
+impl AsmClobberAbi {
+    #[inline]
+    pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
+    #[inline]
+    pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
+    #[inline]
+    pub fn clobber_abi_token(&self) -> Option<SyntaxToken> {
+        support::token(&self.syntax, T![clobber_abi])
+    }
+    #[inline]
+    pub fn string_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![string]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct AsmConst {
+    pub(crate) syntax: SyntaxNode,
+}
+impl AsmConst {
+    #[inline]
+    pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+    #[inline]
+    pub fn const_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![const]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct AsmDirSpec {
+    pub(crate) syntax: SyntaxNode,
+}
+impl AsmDirSpec {
+    #[inline]
+    pub fn in_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![in]) }
+    #[inline]
+    pub fn inlateout_token(&self) -> Option<SyntaxToken> {
+        support::token(&self.syntax, T![inlateout])
+    }
+    #[inline]
+    pub fn inout_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![inout]) }
+    #[inline]
+    pub fn lateout_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![lateout]) }
+    #[inline]
+    pub fn out_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![out]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct AsmExpr {
     pub(crate) syntax: SyntaxNode,
 }
 impl ast::HasAttrs for AsmExpr {}
 impl AsmExpr {
     #[inline]
-    pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+    pub fn asm_pieces(&self) -> AstChildren<AsmPiece> { support::children(&self.syntax) }
+    #[inline]
+    pub fn template(&self) -> AstChildren<Expr> { support::children(&self.syntax) }
     #[inline]
     pub fn pound_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![#]) }
     #[inline]
@@ -79,12 +128,143 @@ impl AsmExpr {
     #[inline]
     pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
     #[inline]
+    pub fn comma_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![,]) }
+    #[inline]
     pub fn asm_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![asm]) }
     #[inline]
     pub fn builtin_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![builtin]) }
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct AsmLabel {
+    pub(crate) syntax: SyntaxNode,
+}
+impl AsmLabel {
+    #[inline]
+    pub fn block_expr(&self) -> Option<BlockExpr> { support::child(&self.syntax) }
+    #[inline]
+    pub fn label_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![label]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct AsmOperandExpr {
+    pub(crate) syntax: SyntaxNode,
+}
+impl AsmOperandExpr {
+    #[inline]
+    pub fn in_expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+    #[inline]
+    pub fn out_expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+    #[inline]
+    pub fn fat_arrow_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=>]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct AsmOperandNamed {
+    pub(crate) syntax: SyntaxNode,
+}
+impl ast::HasName for AsmOperandNamed {}
+impl AsmOperandNamed {
+    #[inline]
+    pub fn asm_operand(&self) -> Option<AsmOperand> { support::child(&self.syntax) }
+    #[inline]
+    pub fn eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct AsmOption {
+    pub(crate) syntax: SyntaxNode,
+}
+impl AsmOption {
+    #[inline]
+    pub fn att_syntax_token(&self) -> Option<SyntaxToken> {
+        support::token(&self.syntax, T![att_syntax])
+    }
+    #[inline]
+    pub fn may_unwind_token(&self) -> Option<SyntaxToken> {
+        support::token(&self.syntax, T![may_unwind])
+    }
+    #[inline]
+    pub fn nomem_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![nomem]) }
+    #[inline]
+    pub fn noreturn_token(&self) -> Option<SyntaxToken> {
+        support::token(&self.syntax, T![noreturn])
+    }
+    #[inline]
+    pub fn nostack_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![nostack]) }
+    #[inline]
+    pub fn preserves_flags_token(&self) -> Option<SyntaxToken> {
+        support::token(&self.syntax, T![preserves_flags])
+    }
+    #[inline]
+    pub fn pure_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![pure]) }
+    #[inline]
+    pub fn raw_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![raw]) }
+    #[inline]
+    pub fn readonly_token(&self) -> Option<SyntaxToken> {
+        support::token(&self.syntax, T![readonly])
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct AsmOptions {
+    pub(crate) syntax: SyntaxNode,
+}
+impl AsmOptions {
+    #[inline]
+    pub fn asm_option(&self) -> Option<AsmOption> { support::child(&self.syntax) }
+    #[inline]
+    pub fn asm_options(&self) -> AstChildren<AsmOption> { support::children(&self.syntax) }
+    #[inline]
+    pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
+    #[inline]
+    pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
+    #[inline]
+    pub fn comma_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![,]) }
+    #[inline]
+    pub fn options_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![options]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct AsmRegOperand {
+    pub(crate) syntax: SyntaxNode,
+}
+impl AsmRegOperand {
+    #[inline]
+    pub fn asm_dir_spec(&self) -> Option<AsmDirSpec> { support::child(&self.syntax) }
+    #[inline]
+    pub fn asm_operand_expr(&self) -> Option<AsmOperandExpr> { support::child(&self.syntax) }
+    #[inline]
+    pub fn asm_reg_spec(&self) -> Option<AsmRegSpec> { support::child(&self.syntax) }
+    #[inline]
+    pub fn l_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['(']) }
+    #[inline]
+    pub fn r_paren_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![')']) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct AsmRegSpec {
+    pub(crate) syntax: SyntaxNode,
+}
+impl AsmRegSpec {
+    #[inline]
+    pub fn name_ref(&self) -> Option<NameRef> { support::child(&self.syntax) }
+    #[inline]
+    pub fn string_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![string]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct AsmSym {
+    pub(crate) syntax: SyntaxNode,
+}
+impl AsmSym {
+    #[inline]
+    pub fn path(&self) -> Option<Path> { support::child(&self.syntax) }
+    #[inline]
+    pub fn sym_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![sym]) }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct AssocItemList {
     pub(crate) syntax: SyntaxNode,
 }
@@ -2052,6 +2232,21 @@ impl ast::HasName for Adt {}
 impl ast::HasVisibility for Adt {}
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum AsmOperand {
+    AsmConst(AsmConst),
+    AsmLabel(AsmLabel),
+    AsmRegOperand(AsmRegOperand),
+    AsmSym(AsmSym),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub enum AsmPiece {
+    AsmClobberAbi(AsmClobberAbi),
+    AsmOperandNamed(AsmOperandNamed),
+    AsmOptions(AsmOptions),
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub enum AssocItem {
     Const(Const),
     Fn(Fn),
@@ -2316,6 +2511,48 @@ impl AstNode for ArrayType {
     #[inline]
     fn syntax(&self) -> &SyntaxNode { &self.syntax }
 }
+impl AstNode for AsmClobberAbi {
+    #[inline]
+    fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_CLOBBER_ABI }
+    #[inline]
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        if Self::can_cast(syntax.kind()) {
+            Some(Self { syntax })
+        } else {
+            None
+        }
+    }
+    #[inline]
+    fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for AsmConst {
+    #[inline]
+    fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_CONST }
+    #[inline]
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        if Self::can_cast(syntax.kind()) {
+            Some(Self { syntax })
+        } else {
+            None
+        }
+    }
+    #[inline]
+    fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for AsmDirSpec {
+    #[inline]
+    fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_DIR_SPEC }
+    #[inline]
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        if Self::can_cast(syntax.kind()) {
+            Some(Self { syntax })
+        } else {
+            None
+        }
+    }
+    #[inline]
+    fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
 impl AstNode for AsmExpr {
     #[inline]
     fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_EXPR }
@@ -2330,6 +2567,118 @@ impl AstNode for AsmExpr {
     #[inline]
     fn syntax(&self) -> &SyntaxNode { &self.syntax }
 }
+impl AstNode for AsmLabel {
+    #[inline]
+    fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_LABEL }
+    #[inline]
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        if Self::can_cast(syntax.kind()) {
+            Some(Self { syntax })
+        } else {
+            None
+        }
+    }
+    #[inline]
+    fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for AsmOperandExpr {
+    #[inline]
+    fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_OPERAND_EXPR }
+    #[inline]
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        if Self::can_cast(syntax.kind()) {
+            Some(Self { syntax })
+        } else {
+            None
+        }
+    }
+    #[inline]
+    fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for AsmOperandNamed {
+    #[inline]
+    fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_OPERAND_NAMED }
+    #[inline]
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        if Self::can_cast(syntax.kind()) {
+            Some(Self { syntax })
+        } else {
+            None
+        }
+    }
+    #[inline]
+    fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for AsmOption {
+    #[inline]
+    fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_OPTION }
+    #[inline]
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        if Self::can_cast(syntax.kind()) {
+            Some(Self { syntax })
+        } else {
+            None
+        }
+    }
+    #[inline]
+    fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for AsmOptions {
+    #[inline]
+    fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_OPTIONS }
+    #[inline]
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        if Self::can_cast(syntax.kind()) {
+            Some(Self { syntax })
+        } else {
+            None
+        }
+    }
+    #[inline]
+    fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for AsmRegOperand {
+    #[inline]
+    fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_REG_OPERAND }
+    #[inline]
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        if Self::can_cast(syntax.kind()) {
+            Some(Self { syntax })
+        } else {
+            None
+        }
+    }
+    #[inline]
+    fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for AsmRegSpec {
+    #[inline]
+    fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_REG_SPEC }
+    #[inline]
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        if Self::can_cast(syntax.kind()) {
+            Some(Self { syntax })
+        } else {
+            None
+        }
+    }
+    #[inline]
+    fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl AstNode for AsmSym {
+    #[inline]
+    fn can_cast(kind: SyntaxKind) -> bool { kind == ASM_SYM }
+    #[inline]
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        if Self::can_cast(syntax.kind()) {
+            Some(Self { syntax })
+        } else {
+            None
+        }
+    }
+    #[inline]
+    fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
 impl AstNode for AssocItemList {
     #[inline]
     fn can_cast(kind: SyntaxKind) -> bool { kind == ASSOC_ITEM_LIST }
@@ -4268,6 +4617,84 @@ impl AstNode for Adt {
         }
     }
 }
+impl From<AsmConst> for AsmOperand {
+    #[inline]
+    fn from(node: AsmConst) -> AsmOperand { AsmOperand::AsmConst(node) }
+}
+impl From<AsmLabel> for AsmOperand {
+    #[inline]
+    fn from(node: AsmLabel) -> AsmOperand { AsmOperand::AsmLabel(node) }
+}
+impl From<AsmRegOperand> for AsmOperand {
+    #[inline]
+    fn from(node: AsmRegOperand) -> AsmOperand { AsmOperand::AsmRegOperand(node) }
+}
+impl From<AsmSym> for AsmOperand {
+    #[inline]
+    fn from(node: AsmSym) -> AsmOperand { AsmOperand::AsmSym(node) }
+}
+impl AstNode for AsmOperand {
+    #[inline]
+    fn can_cast(kind: SyntaxKind) -> bool {
+        matches!(kind, ASM_CONST | ASM_LABEL | ASM_REG_OPERAND | ASM_SYM)
+    }
+    #[inline]
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        let res = match syntax.kind() {
+            ASM_CONST => AsmOperand::AsmConst(AsmConst { syntax }),
+            ASM_LABEL => AsmOperand::AsmLabel(AsmLabel { syntax }),
+            ASM_REG_OPERAND => AsmOperand::AsmRegOperand(AsmRegOperand { syntax }),
+            ASM_SYM => AsmOperand::AsmSym(AsmSym { syntax }),
+            _ => return None,
+        };
+        Some(res)
+    }
+    #[inline]
+    fn syntax(&self) -> &SyntaxNode {
+        match self {
+            AsmOperand::AsmConst(it) => &it.syntax,
+            AsmOperand::AsmLabel(it) => &it.syntax,
+            AsmOperand::AsmRegOperand(it) => &it.syntax,
+            AsmOperand::AsmSym(it) => &it.syntax,
+        }
+    }
+}
+impl From<AsmClobberAbi> for AsmPiece {
+    #[inline]
+    fn from(node: AsmClobberAbi) -> AsmPiece { AsmPiece::AsmClobberAbi(node) }
+}
+impl From<AsmOperandNamed> for AsmPiece {
+    #[inline]
+    fn from(node: AsmOperandNamed) -> AsmPiece { AsmPiece::AsmOperandNamed(node) }
+}
+impl From<AsmOptions> for AsmPiece {
+    #[inline]
+    fn from(node: AsmOptions) -> AsmPiece { AsmPiece::AsmOptions(node) }
+}
+impl AstNode for AsmPiece {
+    #[inline]
+    fn can_cast(kind: SyntaxKind) -> bool {
+        matches!(kind, ASM_CLOBBER_ABI | ASM_OPERAND_NAMED | ASM_OPTIONS)
+    }
+    #[inline]
+    fn cast(syntax: SyntaxNode) -> Option<Self> {
+        let res = match syntax.kind() {
+            ASM_CLOBBER_ABI => AsmPiece::AsmClobberAbi(AsmClobberAbi { syntax }),
+            ASM_OPERAND_NAMED => AsmPiece::AsmOperandNamed(AsmOperandNamed { syntax }),
+            ASM_OPTIONS => AsmPiece::AsmOptions(AsmOptions { syntax }),
+            _ => return None,
+        };
+        Some(res)
+    }
+    #[inline]
+    fn syntax(&self) -> &SyntaxNode {
+        match self {
+            AsmPiece::AsmClobberAbi(it) => &it.syntax,
+            AsmPiece::AsmOperandNamed(it) => &it.syntax,
+            AsmPiece::AsmOptions(it) => &it.syntax,
+        }
+    }
+}
 impl From<Const> for AssocItem {
     #[inline]
     fn from(node: Const) -> AssocItem { AssocItem::Const(node) }
@@ -5803,7 +6230,8 @@ impl AstNode for AnyHasName {
     fn can_cast(kind: SyntaxKind) -> bool {
         matches!(
             kind,
-            CONST
+            ASM_OPERAND_NAMED
+                | CONST
                 | CONST_PARAM
                 | ENUM
                 | FN
@@ -5832,6 +6260,10 @@ impl AstNode for AnyHasName {
     #[inline]
     fn syntax(&self) -> &SyntaxNode { &self.syntax }
 }
+impl From<AsmOperandNamed> for AnyHasName {
+    #[inline]
+    fn from(node: AsmOperandNamed) -> AnyHasName { AnyHasName { syntax: node.syntax } }
+}
 impl From<Const> for AnyHasName {
     #[inline]
     fn from(node: Const) -> AnyHasName { AnyHasName { syntax: node.syntax } }
@@ -6072,6 +6504,16 @@ impl std::fmt::Display for Adt {
         std::fmt::Display::fmt(self.syntax(), f)
     }
 }
+impl std::fmt::Display for AsmOperand {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
+impl std::fmt::Display for AsmPiece {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
 impl std::fmt::Display for AssocItem {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         std::fmt::Display::fmt(self.syntax(), f)
@@ -6142,11 +6584,66 @@ impl std::fmt::Display for ArrayType {
         std::fmt::Display::fmt(self.syntax(), f)
     }
 }
+impl std::fmt::Display for AsmClobberAbi {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
+impl std::fmt::Display for AsmConst {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
+impl std::fmt::Display for AsmDirSpec {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
 impl std::fmt::Display for AsmExpr {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         std::fmt::Display::fmt(self.syntax(), f)
     }
 }
+impl std::fmt::Display for AsmLabel {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
+impl std::fmt::Display for AsmOperandExpr {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
+impl std::fmt::Display for AsmOperandNamed {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
+impl std::fmt::Display for AsmOption {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
+impl std::fmt::Display for AsmOptions {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
+impl std::fmt::Display for AsmRegOperand {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
+impl std::fmt::Display for AsmRegSpec {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
+impl std::fmt::Display for AsmSym {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        std::fmt::Display::fmt(self.syntax(), f)
+    }
+}
 impl std::fmt::Display for AssocItemList {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         std::fmt::Display::fmt(self.syntax(), f)
diff --git a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs
index 4b7e23388c7..fec270a5560 100644
--- a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs
+++ b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs
@@ -1475,6 +1475,19 @@ mod panicking {
 }
 // endregion:panic
 
+// region:asm
+mod arch {
+    #[rustc_builtin_macro]
+    pub macro asm("assembly template", $(operands,)* $(options($(option),*))?) {
+        /* compiler built-in */
+    }
+    #[rustc_builtin_macro]
+    pub macro global_asm("assembly template", $(operands,)* $(options($(option),*))?) {
+        /* compiler built-in */
+    }
+}
+// endregion:asm
+
 #[macro_use]
 mod macros {
     // region:panic
@@ -1487,16 +1500,6 @@ mod macros {
     }
     // endregion:panic
 
-    // region:asm
-    #[macro_export]
-    #[rustc_builtin_macro]
-    macro_rules! asm {
-        ($($arg:tt)*) => {
-            /* compiler built-in */
-        };
-    }
-    // endregion:asm
-
     // region:assert
     #[macro_export]
     #[rustc_builtin_macro]
diff --git a/src/tools/rust-analyzer/xtask/Cargo.toml b/src/tools/rust-analyzer/xtask/Cargo.toml
index 192de869472..4bc1821ee5e 100644
--- a/src/tools/rust-analyzer/xtask/Cargo.toml
+++ b/src/tools/rust-analyzer/xtask/Cargo.toml
@@ -19,6 +19,7 @@ stdx.workspace = true
 proc-macro2 = "1.0.47"
 quote = "1.0.20"
 ungrammar = "1.16.1"
+either.workspace = true
 itertools.workspace = true
 # Avoid adding more dependencies to this crate
 
diff --git a/src/tools/rust-analyzer/xtask/src/codegen/grammar.rs b/src/tools/rust-analyzer/xtask/src/codegen/grammar.rs
index 39e06f9642d..e7534582f2b 100644
--- a/src/tools/rust-analyzer/xtask/src/codegen/grammar.rs
+++ b/src/tools/rust-analyzer/xtask/src/codegen/grammar.rs
@@ -11,9 +11,11 @@ use std::{
     fs,
 };
 
+use either::Either;
 use itertools::Itertools;
 use proc_macro2::{Punct, Spacing};
 use quote::{format_ident, quote};
+use stdx::panic_context;
 use ungrammar::{Grammar, Rule};
 
 use crate::{
@@ -462,6 +464,7 @@ fn generate_syntax_kinds(grammar: KindsSrc) -> String {
 
     let tokens = grammar.tokens.iter().map(|name| format_ident!("{}", name)).collect::<Vec<_>>();
 
+    // FIXME: This generates enum kinds?
     let nodes = grammar.nodes.iter().map(|name| format_ident!("{}", name)).collect::<Vec<_>>();
 
     let ast = quote! {
@@ -711,6 +714,7 @@ fn lower(grammar: &Grammar) -> AstSrc {
     for &node in &nodes {
         let name = grammar[node].name.clone();
         let rule = &grammar[node].rule;
+        let _g = panic_context::enter(name.clone());
         match lower_enum(grammar, rule) {
             Some(variants) => {
                 let enum_src = AstEnumSrc { doc: Vec::new(), name, traits: Vec::new(), variants };
@@ -838,11 +842,16 @@ fn lower_separated_list(
         Rule::Seq(it) => it,
         _ => return false,
     };
-    let (node, repeat, trailing_sep) = match rule.as_slice() {
+
+    let (nt, repeat, trailing_sep) = match rule.as_slice() {
         [Rule::Node(node), Rule::Rep(repeat), Rule::Opt(trailing_sep)] => {
-            (node, repeat, Some(trailing_sep))
+            (Either::Left(node), repeat, Some(trailing_sep))
+        }
+        [Rule::Node(node), Rule::Rep(repeat)] => (Either::Left(node), repeat, None),
+        [Rule::Token(token), Rule::Rep(repeat), Rule::Opt(trailing_sep)] => {
+            (Either::Right(token), repeat, Some(trailing_sep))
         }
-        [Rule::Node(node), Rule::Rep(repeat)] => (node, repeat, None),
+        [Rule::Token(token), Rule::Rep(repeat)] => (Either::Right(token), repeat, None),
         _ => return false,
     };
     let repeat = match &**repeat {
@@ -851,15 +860,28 @@ fn lower_separated_list(
     };
     if !matches!(
         repeat.as_slice(),
-        [comma, Rule::Node(n)]
-            if trailing_sep.map_or(true, |it| comma == &**it) && n == node
+        [comma, nt_]
+            if trailing_sep.map_or(true, |it| comma == &**it) && match (nt, nt_) {
+                (Either::Left(node), Rule::Node(nt_)) => node == nt_,
+                (Either::Right(token), Rule::Token(nt_)) => token == nt_,
+                _ => false,
+            }
     ) {
         return false;
     }
-    let ty = grammar[*node].name.clone();
-    let name = label.cloned().unwrap_or_else(|| pluralize(&to_lower_snake_case(&ty)));
-    let field = Field::Node { name, ty, cardinality: Cardinality::Many };
-    acc.push(field);
+    match nt {
+        Either::Right(token) => {
+            let name = clean_token_name(&grammar[*token].name);
+            let field = Field::Token(name);
+            acc.push(field);
+        }
+        Either::Left(node) => {
+            let ty = grammar[*node].name.clone();
+            let name = label.cloned().unwrap_or_else(|| pluralize(&to_lower_snake_case(&ty)));
+            let field = Field::Node { name, ty, cardinality: Cardinality::Many };
+            acc.push(field);
+        }
+    }
     true
 }
 
diff --git a/src/tools/rust-analyzer/xtask/src/codegen/grammar/ast_src.rs b/src/tools/rust-analyzer/xtask/src/codegen/grammar/ast_src.rs
index 34151bd9587..f1a96e0c6a2 100644
--- a/src/tools/rust-analyzer/xtask/src/codegen/grammar/ast_src.rs
+++ b/src/tools/rust-analyzer/xtask/src/codegen/grammar/ast_src.rs
@@ -113,7 +113,31 @@ const RESERVED: &[&str] = &[
 const CONTEXTUAL_KEYWORDS: &[&str] =
     &["macro_rules", "union", "default", "raw", "dyn", "auto", "yeet"];
 // keywords we use for special macro expansions
-const CONTEXTUAL_BUILTIN_KEYWORDS: &[&str] = &["builtin", "offset_of", "format_args", "asm"];
+const CONTEXTUAL_BUILTIN_KEYWORDS: &[&str] = &[
+    "asm",
+    "att_syntax",
+    "builtin",
+    "clobber_abi",
+    "format_args",
+    // "in",
+    "inlateout",
+    "inout",
+    "label",
+    "lateout",
+    "may_unwind",
+    "nomem",
+    "noreturn",
+    "nostack",
+    "offset_of",
+    "options",
+    "out",
+    "preserves_flags",
+    "pure",
+    // "raw",
+    "readonly",
+    "sym",
+];
+
 // keywords that are keywords depending on the edition
 const EDITION_DEPENDENT_KEYWORDS: &[(&str, Edition)] = &[
     ("try", Edition::Edition2018),