Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/hir-def/src/body/lower.rs15
-rw-r--r--crates/hir-def/src/body/lower/asm.rs230
-rw-r--r--crates/hir-def/src/hir.rs132
-rw-r--r--crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs6
-rw-r--r--crates/hir-expand/src/builtin/fn_macro.rs34
-rw-r--r--crates/hir-ty/src/infer/closure.rs23
-rw-r--r--crates/hir-ty/src/infer/expr.rs67
-rw-r--r--crates/hir-ty/src/infer/mutability.rs27
-rw-r--r--crates/hir-ty/src/tests/macros.rs99
-rw-r--r--crates/hir/src/semantics.rs1
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_strings.html8
-rw-r--r--crates/parser/src/grammar/expressions/atom.rs38
-rw-r--r--crates/syntax/rust.ungram8
-rw-r--r--crates/syntax/src/ast/generated/nodes.rs21
14 files changed, 612 insertions, 97 deletions
diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs
index f2eb43beb1..dfcb8b8120 100644
--- a/crates/hir-def/src/body/lower.rs
+++ b/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,13 +695,7 @@ impl ExprCollector<'_> {
}
}
ast::Expr::UnderscoreExpr(_) => self.alloc_expr(Expr::Underscore, syntax_ptr),
- ast::Expr::AsmExpr(e) => {
- let template = e.template().map(|it| self.collect_expr(it)).collect();
- self.alloc_expr(
- Expr::InlineAsm(InlineAsm { template, operands: Box::default() }),
- 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();
@@ -2064,6 +2060,7 @@ impl ExprCollector<'_> {
is_assignee_expr: false,
})
}
+
// endregion: format
fn lang_path(&self, lang: LangItem) -> Option<Path> {
diff --git a/crates/hir-def/src/body/lower/asm.rs b/crates/hir-def/src/body/lower/asm.rs
new file mode 100644
index 0000000000..dafa3a859d
--- /dev/null
+++ b/crates/hir-def/src/body/lower/asm.rs
@@ -0,0 +1,230 @@
+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 operand in asm.asm_operands() {
+ 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 operand {
+ ast::AsmOperand::AsmClobberAbi(clobber_abi) => {
+ if let Some(abi_name) = clobber_abi.string_token() {
+ clobber_abis.insert(Symbol::intern(abi_name.text()));
+ }
+ continue;
+ }
+ ast::AsmOperand::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::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 let Some(name) = op.name() {
+ let sym = Symbol::intern(&name.text());
+ named_args.insert(sym.clone(), slot);
+ named_pos.insert(slot, sym);
+ }
+ 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 = 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
+ } else {
+ Some(idx)
+ }
+ }
+ rustc_parse_format::ArgumentNamed(name) => {
+ let name = Symbol::intern(name);
+ if let Some(position_span) = to_span(arg.position_span) {
+ mappings.push((
+ position_span,
+ Name::new_symbol_root(name.clone()),
+ ));
+ }
+ named_args.get(&name).copied()
+ }
+ };
+ }
+ }
+ }
+ })
+ };
+ let idx = self.alloc_expr(
+ Expr::InlineAsm(InlineAsm { operands: operands.into_boxed_slice(), options }),
+ syntax_ptr,
+ );
+ self.source_map.format_args_template_map.insert(idx, mappings);
+ idx
+ }
+}
diff --git a/crates/hir-def/src/hir.rs b/crates/hir-def/src/hir.rs
index 8f537672b5..7d2c573ebf 100644
--- a/crates/hir-def/src/hir.rs
+++ b/crates/hir-def/src/hir.rs
@@ -307,8 +307,120 @@ pub struct OffsetOf {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InlineAsm {
- pub template: Box<[ExprId]>,
- pub operands: Box<[()]>,
+ pub operands: Box<[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)]
@@ -373,7 +485,21 @@ impl Expr {
match self {
Expr::Missing => {}
Expr::Path(_) | Expr::OffsetOf(_) => {}
- Expr::InlineAsm(it) => it.template.iter().copied().for_each(f),
+ 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/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
index b6dbba12cd..37ae1ab39b 100644
--- a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
+++ b/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/crates/hir-expand/src/builtin/fn_macro.rs b/crates/hir-expand/src/builtin/fn_macro.rs
index 795d9b14df..15fed45caf 100644
--- a/crates/hir-expand/src/builtin/fn_macro.rs
+++ b/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/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs
index 67a3d2434d..3ad330c1a0 100644
--- a/crates/hir-ty/src/infer/closure.rs
+++ b/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,9 +669,21 @@ impl InferenceContext<'_> {
fn walk_expr_without_adjust(&mut self, tgt_expr: ExprId) {
match &self.body[tgt_expr] {
Expr::OffsetOf(_) => (),
- Expr::InlineAsm(e) => {
- e.template.iter().for_each(|it| self.walk_expr_without_adjust(*it))
- }
+ 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/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index 6b725d690d..e6eaf2f446 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/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) => {
- it.template.iter().for_each(|&expr| _ = self.infer_expr_no_expect(expr));
- 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/crates/hir-ty/src/infer/mutability.rs b/crates/hir-ty/src/infer/mutability.rs
index e1b460d072..e841c66308 100644
--- a/crates/hir-ty/src/infer/mutability.rs
+++ b/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,10 +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) => e
- .template
- .iter()
- .for_each(|&expr| self.infer_mut_expr_without_adjust(expr, 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);
diff --git a/crates/hir-ty/src/tests/macros.rs b/crates/hir-ty/src/tests/macros.rs
index 5a6161f942..83aceec981 100644
--- a/crates/hir-ty/src/tests/macros.rs
+++ b/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() {
+ asm!("")
+}
+"#,
+ );
+}
+
+#[test]
+fn asm_no_return() {
+ check_no_mismatches(
+ r#"
+//- minicore: asm
+fn unit() -> ! {
+ asm!("", options(noreturn))
+}
+"#,
+ );
+}
+
+#[test]
+fn asm_things() {
+ check_infer(
+ r#"
+//- minicore: asm, concat
+fn main() {
+ unsafe {
+ let foo = 1;
+ let mut o = 0;
+ asm!(
+ "%input = OpLoad _ {0}",
+ concat!("%result = ", bar, " _ %input"),
+ "OpStore {1} %result",
+ in(reg) &foo,
+ in(reg) &mut o,
+ );
+ o
+
+ let thread_id: usize;
+ 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;
+ 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..190 'builti...tack))': ()
+ !0..449 'builti...urn),)': !
+ 10..1254 '{ ... } }': ()
+ 16..1252 'unsafe... }': ()
+ 37..40 'foo': i32
+ 43..44 '1': i32
+ 58..63 'mut o': i32
+ 66..67 '0': i32
+ 281..282 'o': i32
+ 296..305 'thread_id': usize
+ "#]],
+ )
+}
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index c78b59826c..4e6887295c 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -368,7 +368,6 @@ impl<'db> SemanticsImpl<'db> {
| BuiltinFnLikeExpander::File
| BuiltinFnLikeExpander::ModulePath
| BuiltinFnLikeExpander::Asm
- | BuiltinFnLikeExpander::LlvmAsm
| BuiltinFnLikeExpander::GlobalAsm
| BuiltinFnLikeExpander::LogSyntax
| BuiltinFnLikeExpander::TraceMacros
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
index cb47fc68bc..9376ef65a4 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
@@ -166,10 +166,10 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
<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="string_literal macro">"mov {0}, {1}"</span><span class="comma macro">,</span>
+ <span class="string_literal macro">"add {0}, 5"</span><span class="comma macro">,</span>
+ <span class="keyword macro">out</span><span class="parenthesis macro">(</span><span class="unresolved_reference 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="unresolved_reference 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>
diff --git a/crates/parser/src/grammar/expressions/atom.rs b/crates/parser/src/grammar/expressions/atom.rs
index 57f1e6e9f0..f98ca5f403 100644
--- a/crates/parser/src/grammar/expressions/atom.rs
+++ b/crates/parser/src/grammar/expressions/atom.rs
@@ -329,9 +329,11 @@ fn parse_asm_expr(p: &mut Parser<'_>, m: Marker) -> Option<CompletedMarker> {
break;
}
+ let op = p.start();
// Parse clobber_abi
if p.eat_contextual_kw(T![clobber_abi]) {
parse_clobber_abi(p);
+ op.complete(p, ASM_CLOBBER_ABI);
allow_templates = false;
continue;
}
@@ -339,6 +341,7 @@ fn parse_asm_expr(p: &mut Parser<'_>, m: Marker) -> Option<CompletedMarker> {
// Parse options
if p.eat_contextual_kw(T![options]) {
parse_options(p);
+ op.complete(p, ASM_OPTIONS);
allow_templates = false;
continue;
}
@@ -353,27 +356,14 @@ fn parse_asm_expr(p: &mut Parser<'_>, m: Marker) -> Option<CompletedMarker> {
false
};
- let op = p.start();
- if p.eat(T![in]) {
- parse_reg(p);
- expr(p);
- op.complete(p, ASM_REG_OPERAND);
- } else if p.eat_contextual_kw(T![out]) {
- parse_reg(p);
- expr(p);
- op.complete(p, ASM_REG_OPERAND);
- } else if p.eat_contextual_kw(T![lateout]) {
+ 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);
- } else if p.eat_contextual_kw(T![inout]) {
- parse_reg(p);
- expr(p);
- if p.eat(T![=>]) {
- expr(p);
- }
- op.complete(p, ASM_REG_OPERAND);
- } else if p.eat_contextual_kw(T![inlateout]) {
+ } 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![=>]) {
@@ -381,21 +371,26 @@ fn parse_asm_expr(p: &mut Parser<'_>, m: Marker) -> Option<CompletedMarker> {
}
op.complete(p, ASM_REG_OPERAND);
} else if p.eat_contextual_kw(T![label]) {
+ dir_spec.abandon(p);
block_expr(p);
op.complete(p, ASM_LABEL);
} else if p.eat(T![const]) {
+ dir_spec.abandon(p);
expr(p);
op.complete(p, ASM_CONST);
} else if p.eat_contextual_kw(T![sym]) {
- expr(p);
+ dir_spec.abandon(p);
+ paths::type_path(p);
op.complete(p, ASM_SYM);
} else if allow_templates {
+ dir_spec.abandon(p);
op.abandon(p);
if expr(p).is_none() {
p.err_and_bump("expected asm template");
}
continue;
} else {
+ dir_spec.abandon(p);
op.abandon(p);
p.err_and_bump("expected asm operand");
if p.at(T!['}']) {
@@ -424,11 +419,12 @@ fn parse_options(p: &mut Parser<'_>) {
T![att_syntax],
T![raw],
];
-
- if !OPTIONS.iter().any(|&syntax| p.eat(syntax)) {
+ 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![')']) {
diff --git a/crates/syntax/rust.ungram b/crates/syntax/rust.ungram
index 4d780ba28f..5dace66c32 100644
--- a/crates/syntax/rust.ungram
+++ b/crates/syntax/rust.ungram
@@ -411,13 +411,11 @@ AsmClobberAbi = 'clobber_abi' '(' ('@string' (',' '@string')* ','?) ')'
AsmOption = 'pure' | 'nomem' | 'readonly' | 'preserves_flags' | 'noreturn' | 'nostack' | 'att_syntax' | 'raw' | 'may_unwind'
// options := "options(" option *("," option) [","] ")"
AsmOptions = 'options' '(' AsmOption *(',' AsmOption) ','? ')'
-// operand := reg_operand / clobber_abi / options
-AsmOperand = AsmRegOperand | AsmClobberAbi | AsmOptions | AsmLabel
AsmLabel = 'label' BlockExpr
-AsmSym = 'sym' Expr
+AsmSym = 'sym' Path
AsmConst = 'const' Expr
-
-
+// operand := reg_operand / clobber_abi / options
+AsmOperand = AsmRegOperand | AsmClobberAbi | AsmOptions | AsmLabel | AsmSym | AsmConst
FormatArgsExpr =
Attr* 'builtin' '#' 'format_args' '('
diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs
index e5e1115e05..01d47c34bb 100644
--- a/crates/syntax/src/ast/generated/nodes.rs
+++ b/crates/syntax/src/ast/generated/nodes.rs
@@ -250,7 +250,7 @@ pub struct AsmSym {
}
impl AsmSym {
#[inline]
- pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) }
+ pub fn path(&self) -> Option<Path> { support::child(&self.syntax) }
#[inline]
pub fn sym_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![sym]) }
}
@@ -2225,9 +2225,11 @@ impl ast::HasVisibility for Adt {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum AsmOperand {
AsmClobberAbi(AsmClobberAbi),
+ AsmConst(AsmConst),
AsmLabel(AsmLabel),
AsmOptions(AsmOptions),
AsmRegOperand(AsmRegOperand),
+ AsmSym(AsmSym),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -4591,6 +4593,10 @@ impl From<AsmClobberAbi> for AsmOperand {
#[inline]
fn from(node: AsmClobberAbi) -> AsmOperand { AsmOperand::AsmClobberAbi(node) }
}
+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) }
@@ -4603,18 +4609,27 @@ 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_CLOBBER_ABI | ASM_LABEL | ASM_OPTIONS | ASM_REG_OPERAND)
+ matches!(
+ kind,
+ ASM_CLOBBER_ABI | ASM_CONST | ASM_LABEL | ASM_OPTIONS | ASM_REG_OPERAND | ASM_SYM
+ )
}
#[inline]
fn cast(syntax: SyntaxNode) -> Option<Self> {
let res = match syntax.kind() {
ASM_CLOBBER_ABI => AsmOperand::AsmClobberAbi(AsmClobberAbi { syntax }),
+ ASM_CONST => AsmOperand::AsmConst(AsmConst { syntax }),
ASM_LABEL => AsmOperand::AsmLabel(AsmLabel { syntax }),
ASM_OPTIONS => AsmOperand::AsmOptions(AsmOptions { syntax }),
ASM_REG_OPERAND => AsmOperand::AsmRegOperand(AsmRegOperand { syntax }),
+ ASM_SYM => AsmOperand::AsmSym(AsmSym { syntax }),
_ => return None,
};
Some(res)
@@ -4623,9 +4638,11 @@ impl AstNode for AsmOperand {
fn syntax(&self) -> &SyntaxNode {
match self {
AsmOperand::AsmClobberAbi(it) => &it.syntax,
+ AsmOperand::AsmConst(it) => &it.syntax,
AsmOperand::AsmLabel(it) => &it.syntax,
AsmOperand::AsmOptions(it) => &it.syntax,
AsmOperand::AsmRegOperand(it) => &it.syntax,
+ AsmOperand::AsmSym(it) => &it.syntax,
}
}
}