-
Notifications
You must be signed in to change notification settings - Fork 12.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto merge of #18022 - Veykril:asm-parse, r=Veykril
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
Showing
49 changed files
with
2,070 additions
and
164 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ extend-ignore-re = [ | |
'"flate2"', | ||
"raison d'être", | ||
"inout", | ||
"INOUT", | ||
"optin" | ||
] | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
254 changes: 254 additions & 0 deletions
254
src/tools/rust-analyzer/crates/hir-def/src/body/lower/asm.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
Oops, something went wrong.