mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-01 11:31:15 +00:00
284 lines
14 KiB
Rust
284 lines
14 KiB
Rust
//! Lowering of inline assembly.
|
|
use hir_expand::name::Name;
|
|
use intern::Symbol;
|
|
use rustc_hash::{FxHashMap, FxHashSet};
|
|
use syntax::{
|
|
AstNode, AstPtr, AstToken, T,
|
|
ast::{self, HasName, IsString},
|
|
};
|
|
use tt::TextRange;
|
|
|
|
use crate::{
|
|
expr_store::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 = op
|
|
.asm_operand_expr()
|
|
.and_then(|it| it.in_expr())
|
|
.filter(|it| !matches!(it, ast::Expr::UnderscoreExpr(_)))
|
|
.map(|expr| self.collect_expr(expr));
|
|
AsmOperand::Out { reg, expr, late: false }
|
|
} else if dir_spec.lateout_token().is_some() {
|
|
let expr = op
|
|
.asm_operand_expr()
|
|
.and_then(|it| it.in_expr())
|
|
.filter(|it| !matches!(it, ast::Expr::UnderscoreExpr(_)))
|
|
.map(|expr| self.collect_expr(expr));
|
|
|
|
AsmOperand::Out { reg, 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());
|
|
match op_expr.fat_arrow_token().is_some() {
|
|
true => {
|
|
let out_expr = op_expr
|
|
.out_expr()
|
|
.filter(|it| {
|
|
!matches!(it, ast::Expr::UnderscoreExpr(_))
|
|
})
|
|
.map(|expr| self.collect_expr(expr));
|
|
|
|
AsmOperand::SplitInOut {
|
|
reg,
|
|
in_expr,
|
|
out_expr,
|
|
late: false,
|
|
}
|
|
}
|
|
false => {
|
|
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());
|
|
match op_expr.fat_arrow_token().is_some() {
|
|
true => {
|
|
let out_expr = op_expr
|
|
.out_expr()
|
|
.filter(|it| {
|
|
!matches!(it, ast::Expr::UnderscoreExpr(_))
|
|
})
|
|
.map(|expr| self.collect_expr(expr));
|
|
|
|
AsmOperand::SplitInOut {
|
|
reg,
|
|
in_expr,
|
|
out_expr,
|
|
late: true,
|
|
}
|
|
}
|
|
false => {
|
|
AsmOperand::InOut { reg, expr: in_expr, late: true }
|
|
}
|
|
}
|
|
} 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.lower_path(
|
|
p,
|
|
&mut ExprCollector::impl_trait_error_allocator,
|
|
)
|
|
}) 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()
|
|
.enumerate()
|
|
.filter_map(|(idx, it)| Some((idx, it.clone(), self.expand_macros_to_string(it)?)))
|
|
.for_each(|(idx, expr, (s, is_direct_literal))| {
|
|
mappings.resize_with(idx + 1, Vec::default);
|
|
let Ok(text) = s.value() else {
|
|
return;
|
|
};
|
|
let mappings = &mut mappings[idx];
|
|
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: std::ops::Range<usize>| {
|
|
is_direct_literal.then(|| {
|
|
TextRange::new(
|
|
inner_span.start.try_into().unwrap(),
|
|
inner_span.end.try_into().unwrap(),
|
|
)
|
|
})
|
|
};
|
|
for piece in unverified_pieces {
|
|
match piece {
|
|
rustc_parse_format::Piece::Lit(_) => {}
|
|
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)
|
|
.asm_to_captures
|
|
.insert(idx, mappings);
|
|
idx
|
|
}
|
|
}
|