Skip to content

Commit

Permalink
Auto merge of #18022 - Veykril:asm-parse, r=Veykril
Browse files Browse the repository at this point in the history
feat: IDE support for `asm!` expressions

Fixes rust-lang/rust-analyzer#10461, Fixes rust-lang/rust-analyzer#6031 Progresses rust-lang/rust-analyzer#11621

Notably this only works for asm expressions not items yet. Most IDE features work, mainly completions need extra logic still.
  • Loading branch information
bors committed Sep 5, 2024
2 parents 1c26c99 + 72980c4 commit bdfb2f6
Show file tree
Hide file tree
Showing 49 changed files with 2,070 additions and 164 deletions.
1 change: 1 addition & 0 deletions src/tools/rust-analyzer/.typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ extend-ignore-re = [
'"flate2"',
"raison d'être",
"inout",
"INOUT",
"optin"
]

Expand Down
1 change: 1 addition & 0 deletions src/tools/rust-analyzer/Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2624,6 +2624,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"directories",
"either",
"flate2",
"itertools",
"proc-macro2",
Expand Down
1 change: 1 addition & 0 deletions src/tools/rust-analyzer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 32 additions & 4 deletions src/tools/rust-analyzer/crates/hir-def/src/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,

Expand Down Expand Up @@ -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.
Expand All @@ -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();
Expand All @@ -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()
}
}
14 changes: 7 additions & 7 deletions src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Transforms `ast::Expr` into an equivalent `hir_def::expr::Expr`
//! representation.

mod asm;

use std::mem;

use base_db::CrateId;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -2061,6 +2060,7 @@ impl ExprCollector<'_> {
is_assignee_expr: false,
})
}

// endregion: format

fn lang_path(&self, lang: LangItem) -> Option<Path> {
Expand Down
254 changes: 254 additions & 0 deletions src/tools/rust-analyzer/crates/hir-def/src/body/lower/asm.rs
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading

0 comments on commit bdfb2f6

Please sign in to comment.