Skip to content

Commit d927daf

Browse files
committed
Auto merge of #18022 - Veykril:asm-parse, r=Veykril
feat: IDE support for `asm!` expressions Fixes #10461, Fixes #6031 Progresses #11621 Notably this only works for asm expressions not items yet. Most IDE features work, mainly completions need extra logic still.
2 parents 50882fb + c075a99 commit d927daf

File tree

49 files changed

+2070
-164
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+2070
-164
lines changed

.typos.toml

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ extend-ignore-re = [
1515
'"flate2"',
1616
"raison d'être",
1717
"inout",
18+
"INOUT",
1819
"optin"
1920
]
2021

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ style = { level = "warn", priority = -1 }
185185
suspicious = { level = "warn", priority = -1 }
186186

187187
## allow following lints
188+
too_long_first_doc_paragraph = "allow"
188189
# subjective
189190
single_match = "allow"
190191
# () makes a fine error in most cases

crates/hir-def/src/body.rs

+32-4
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,14 @@ pub struct BodySourceMap {
100100
field_map_back: FxHashMap<ExprId, FieldSource>,
101101
pat_field_map_back: FxHashMap<PatId, PatFieldSource>,
102102

103-
format_args_template_map: FxHashMap<ExprId, Vec<(syntax::TextRange, Name)>>,
103+
template_map: Option<
104+
Box<(
105+
// format_args!
106+
FxHashMap<ExprId, Vec<(syntax::TextRange, Name)>>,
107+
// asm!
108+
FxHashMap<ExprId, Vec<(syntax::TextRange, usize)>>,
109+
)>,
110+
>,
104111

105112
expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, MacroFileId>,
106113

@@ -426,7 +433,16 @@ impl BodySourceMap {
426433
node: InFile<&ast::FormatArgsExpr>,
427434
) -> Option<&[(syntax::TextRange, Name)]> {
428435
let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::Expr>);
429-
self.format_args_template_map.get(self.expr_map.get(&src)?).map(std::ops::Deref::deref)
436+
self.template_map.as_ref()?.0.get(self.expr_map.get(&src)?).map(std::ops::Deref::deref)
437+
}
438+
439+
pub fn asm_template_args(
440+
&self,
441+
node: InFile<&ast::AsmExpr>,
442+
) -> Option<(ExprId, &[(syntax::TextRange, usize)])> {
443+
let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::Expr>);
444+
let expr = self.expr_map.get(&src)?;
445+
Some(*expr).zip(self.template_map.as_ref()?.1.get(expr).map(std::ops::Deref::deref))
430446
}
431447

432448
/// Get a reference to the body source map's diagnostics.
@@ -446,11 +462,14 @@ impl BodySourceMap {
446462
field_map_back,
447463
pat_field_map_back,
448464
expansions,
449-
format_args_template_map,
465+
template_map,
450466
diagnostics,
451467
binding_definitions,
452468
} = self;
453-
format_args_template_map.shrink_to_fit();
469+
if let Some(template_map) = template_map {
470+
template_map.0.shrink_to_fit();
471+
template_map.1.shrink_to_fit();
472+
}
454473
expr_map.shrink_to_fit();
455474
expr_map_back.shrink_to_fit();
456475
pat_map.shrink_to_fit();
@@ -463,4 +482,13 @@ impl BodySourceMap {
463482
diagnostics.shrink_to_fit();
464483
binding_definitions.shrink_to_fit();
465484
}
485+
486+
pub fn template_map(
487+
&self,
488+
) -> Option<&(
489+
FxHashMap<Idx<Expr>, Vec<(tt::TextRange, Name)>>,
490+
FxHashMap<Idx<Expr>, Vec<(tt::TextRange, usize)>>,
491+
)> {
492+
self.template_map.as_deref()
493+
}
466494
}

crates/hir-def/src/body/lower.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
//! Transforms `ast::Expr` into an equivalent `hir_def::expr::Expr`
22
//! representation.
33
4+
mod asm;
5+
46
use std::mem;
57

68
use base_db::CrateId;
@@ -35,8 +37,8 @@ use crate::{
3537
FormatPlaceholder, FormatSign, FormatTrait,
3638
},
3739
Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, ClosureKind,
38-
Expr, ExprId, InlineAsm, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability,
39-
OffsetOf, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
40+
Expr, ExprId, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability, OffsetOf, Pat,
41+
PatId, RecordFieldPat, RecordLitField, Statement,
4042
},
4143
item_scope::BuiltinShadowMode,
4244
lang_item::LangItem,
@@ -693,10 +695,7 @@ impl ExprCollector<'_> {
693695
}
694696
}
695697
ast::Expr::UnderscoreExpr(_) => self.alloc_expr(Expr::Underscore, syntax_ptr),
696-
ast::Expr::AsmExpr(e) => {
697-
let e = self.collect_expr_opt(e.expr());
698-
self.alloc_expr(Expr::InlineAsm(InlineAsm { e }), syntax_ptr)
699-
}
698+
ast::Expr::AsmExpr(e) => self.lower_inline_asm(e, syntax_ptr),
700699
ast::Expr::OffsetOfExpr(e) => {
701700
let container = Interned::new(TypeRef::from_ast_opt(&self.ctx(), e.ty()));
702701
let fields = e.fields().map(|it| it.as_name()).collect();
@@ -1848,7 +1847,7 @@ impl ExprCollector<'_> {
18481847
},
18491848
syntax_ptr,
18501849
);
1851-
self.source_map.format_args_template_map.insert(idx, mappings);
1850+
self.source_map.template_map.get_or_insert_with(Default::default).0.insert(idx, mappings);
18521851
idx
18531852
}
18541853

@@ -2061,6 +2060,7 @@ impl ExprCollector<'_> {
20612060
is_assignee_expr: false,
20622061
})
20632062
}
2063+
20642064
// endregion: format
20652065

20662066
fn lang_path(&self, lang: LangItem) -> Option<Path> {

crates/hir-def/src/body/lower/asm.rs

+254
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
//! Lowering of inline assembly.
2+
use hir_expand::name::Name;
3+
use intern::Symbol;
4+
use rustc_hash::{FxHashMap, FxHashSet};
5+
use syntax::{
6+
ast::{self, HasName, IsString},
7+
AstNode, AstPtr, AstToken, T,
8+
};
9+
use tt::{TextRange, TextSize};
10+
11+
use crate::{
12+
body::lower::{ExprCollector, FxIndexSet},
13+
hir::{AsmOperand, AsmOptions, Expr, ExprId, InlineAsm, InlineAsmRegOrRegClass},
14+
};
15+
16+
impl ExprCollector<'_> {
17+
pub(super) fn lower_inline_asm(
18+
&mut self,
19+
asm: ast::AsmExpr,
20+
syntax_ptr: AstPtr<ast::Expr>,
21+
) -> ExprId {
22+
let mut clobber_abis = FxIndexSet::default();
23+
let mut operands = vec![];
24+
let mut options = AsmOptions::empty();
25+
26+
let mut named_pos: FxHashMap<usize, Symbol> = Default::default();
27+
let mut named_args: FxHashMap<Symbol, usize> = Default::default();
28+
let mut reg_args: FxHashSet<usize> = Default::default();
29+
for piece in asm.asm_pieces() {
30+
let slot = operands.len();
31+
let mut lower_reg = |reg: Option<ast::AsmRegSpec>| {
32+
let reg = reg?;
33+
if let Some(string) = reg.string_token() {
34+
reg_args.insert(slot);
35+
Some(InlineAsmRegOrRegClass::Reg(Symbol::intern(string.text())))
36+
} else {
37+
reg.name_ref().map(|name_ref| {
38+
InlineAsmRegOrRegClass::RegClass(Symbol::intern(&name_ref.text()))
39+
})
40+
}
41+
};
42+
43+
let op = match piece {
44+
ast::AsmPiece::AsmClobberAbi(clobber_abi) => {
45+
if let Some(abi_name) = clobber_abi.string_token() {
46+
clobber_abis.insert(Symbol::intern(abi_name.text()));
47+
}
48+
continue;
49+
}
50+
ast::AsmPiece::AsmOptions(opt) => {
51+
opt.asm_options().for_each(|opt| {
52+
options |= match opt.syntax().first_token().map_or(T![$], |it| it.kind()) {
53+
T![att_syntax] => AsmOptions::ATT_SYNTAX,
54+
T![may_unwind] => AsmOptions::MAY_UNWIND,
55+
T![nomem] => AsmOptions::NOMEM,
56+
T![noreturn] => AsmOptions::NORETURN,
57+
T![nostack] => AsmOptions::NOSTACK,
58+
T![preserves_flags] => AsmOptions::PRESERVES_FLAGS,
59+
T![pure] => AsmOptions::PURE,
60+
T![raw] => AsmOptions::RAW,
61+
T![readonly] => AsmOptions::READONLY,
62+
_ => return,
63+
}
64+
});
65+
continue;
66+
}
67+
ast::AsmPiece::AsmOperandNamed(op) => {
68+
let name = op.name().map(|name| Symbol::intern(&name.text()));
69+
if let Some(name) = &name {
70+
named_args.insert(name.clone(), slot);
71+
named_pos.insert(slot, name.clone());
72+
}
73+
let Some(op) = op.asm_operand() else { continue };
74+
(
75+
name.map(Name::new_symbol_root),
76+
match op {
77+
ast::AsmOperand::AsmRegOperand(op) => {
78+
let Some(dir_spec) = op.asm_dir_spec() else {
79+
continue;
80+
};
81+
let Some(reg) = lower_reg(op.asm_reg_spec()) else {
82+
continue;
83+
};
84+
if dir_spec.in_token().is_some() {
85+
let expr = self.collect_expr_opt(
86+
op.asm_operand_expr().and_then(|it| it.in_expr()),
87+
);
88+
AsmOperand::In { reg, expr }
89+
} else if dir_spec.out_token().is_some() {
90+
let expr = self.collect_expr_opt(
91+
op.asm_operand_expr().and_then(|it| it.in_expr()),
92+
);
93+
AsmOperand::Out { reg, expr: Some(expr), late: false }
94+
} else if dir_spec.lateout_token().is_some() {
95+
let expr = self.collect_expr_opt(
96+
op.asm_operand_expr().and_then(|it| it.in_expr()),
97+
);
98+
AsmOperand::Out { reg, expr: Some(expr), late: true }
99+
} else if dir_spec.inout_token().is_some() {
100+
let Some(op_expr) = op.asm_operand_expr() else { continue };
101+
let in_expr = self.collect_expr_opt(op_expr.in_expr());
102+
let out_expr =
103+
op_expr.out_expr().map(|it| self.collect_expr(it));
104+
match out_expr {
105+
Some(out_expr) => AsmOperand::SplitInOut {
106+
reg,
107+
in_expr,
108+
out_expr: Some(out_expr),
109+
late: false,
110+
},
111+
None => {
112+
AsmOperand::InOut { reg, expr: in_expr, late: false }
113+
}
114+
}
115+
} else if dir_spec.inlateout_token().is_some() {
116+
let Some(op_expr) = op.asm_operand_expr() else { continue };
117+
let in_expr = self.collect_expr_opt(op_expr.in_expr());
118+
let out_expr =
119+
op_expr.out_expr().map(|it| self.collect_expr(it));
120+
match out_expr {
121+
Some(out_expr) => AsmOperand::SplitInOut {
122+
reg,
123+
in_expr,
124+
out_expr: Some(out_expr),
125+
late: false,
126+
},
127+
None => {
128+
AsmOperand::InOut { reg, expr: in_expr, late: false }
129+
}
130+
}
131+
} else {
132+
continue;
133+
}
134+
}
135+
ast::AsmOperand::AsmLabel(l) => {
136+
AsmOperand::Label(self.collect_block_opt(l.block_expr()))
137+
}
138+
ast::AsmOperand::AsmConst(c) => {
139+
AsmOperand::Const(self.collect_expr_opt(c.expr()))
140+
}
141+
ast::AsmOperand::AsmSym(s) => {
142+
let Some(path) =
143+
s.path().and_then(|p| self.expander.parse_path(self.db, p))
144+
else {
145+
continue;
146+
};
147+
AsmOperand::Sym(path)
148+
}
149+
},
150+
)
151+
}
152+
};
153+
operands.push(op);
154+
}
155+
156+
let mut mappings = vec![];
157+
let mut curarg = 0;
158+
if !options.contains(AsmOptions::RAW) {
159+
// Don't treat raw asm as a format string.
160+
asm.template()
161+
.filter_map(|it| Some((it.clone(), self.expand_macros_to_string(it)?)))
162+
.for_each(|(expr, (s, is_direct_literal))| {
163+
let Ok(text) = s.value() else {
164+
return;
165+
};
166+
let template_snippet = match expr {
167+
ast::Expr::Literal(literal) => match literal.kind() {
168+
ast::LiteralKind::String(s) => Some(s.text().to_owned()),
169+
_ => None,
170+
},
171+
_ => None,
172+
};
173+
let str_style = match s.quote_offsets() {
174+
Some(offsets) => {
175+
let raw = usize::from(offsets.quotes.0.len()) - 1;
176+
// subtract 1 for the `r` prefix
177+
(raw != 0).then(|| raw - 1)
178+
}
179+
None => None,
180+
};
181+
182+
let mut parser = rustc_parse_format::Parser::new(
183+
&text,
184+
str_style,
185+
template_snippet,
186+
false,
187+
rustc_parse_format::ParseMode::InlineAsm,
188+
);
189+
parser.curarg = curarg;
190+
191+
let mut unverified_pieces = Vec::new();
192+
while let Some(piece) = parser.next() {
193+
if !parser.errors.is_empty() {
194+
break;
195+
} else {
196+
unverified_pieces.push(piece);
197+
}
198+
}
199+
200+
curarg = parser.curarg;
201+
202+
let to_span = |inner_span: rustc_parse_format::InnerSpan| {
203+
is_direct_literal.then(|| {
204+
TextRange::new(
205+
inner_span.start.try_into().unwrap(),
206+
inner_span.end.try_into().unwrap(),
207+
) - TextSize::from(str_style.map(|it| it + 1).unwrap_or(0) as u32 + 1)
208+
})
209+
};
210+
for piece in unverified_pieces {
211+
match piece {
212+
rustc_parse_format::Piece::String(_) => {}
213+
rustc_parse_format::Piece::NextArgument(arg) => {
214+
// let span = arg_spans.next();
215+
216+
let (operand_idx, _name) = match arg.position {
217+
rustc_parse_format::ArgumentIs(idx)
218+
| rustc_parse_format::ArgumentImplicitlyIs(idx) => {
219+
if idx >= operands.len()
220+
|| named_pos.contains_key(&idx)
221+
|| reg_args.contains(&idx)
222+
{
223+
(None, None)
224+
} else {
225+
(Some(idx), None)
226+
}
227+
}
228+
rustc_parse_format::ArgumentNamed(name) => {
229+
let name = Symbol::intern(name);
230+
(
231+
named_args.get(&name).copied(),
232+
Some(Name::new_symbol_root(name)),
233+
)
234+
}
235+
};
236+
237+
if let Some(operand_idx) = operand_idx {
238+
if let Some(position_span) = to_span(arg.position_span) {
239+
mappings.push((position_span, operand_idx));
240+
}
241+
}
242+
}
243+
}
244+
}
245+
})
246+
};
247+
let idx = self.alloc_expr(
248+
Expr::InlineAsm(InlineAsm { operands: operands.into_boxed_slice(), options }),
249+
syntax_ptr,
250+
);
251+
self.source_map.template_map.get_or_insert_with(Default::default).1.insert(idx, mappings);
252+
idx
253+
}
254+
}

0 commit comments

Comments
 (0)