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 { 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, + ) -> ExprId { + let mut clobber_abis = FxIndexSet::default(); + let mut operands = vec![]; + let mut options = AsmOptions::empty(); + + let mut named_pos: FxHashMap = Default::default(); + let mut named_args: FxHashMap = Default::default(); + let mut reg_args: FxHashSet = Default::default(); + for operand in asm.asm_operands() { + let slot = operands.len(); + let mut lower_reg = |reg: Option| { + 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, + late: bool, + }, + InOut { + reg: InlineAsmRegOrRegClass, + expr: ExprId, + late: bool, + }, + SplitInOut { + reg: InlineAsmRegOrRegClass, + in_expr: ExprId, + out_expr: Option, + 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 { - // 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 { - // 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 let i: u64 = 3; let o: u64; asm!( - "mov {0}, {1}", - "add {0}, 5", - out(reg) o, - in(reg) i, + "mov {0}, {1}", + "add {0}, 5", + out(reg) o, + in(reg) i, ); const CONSTANT: () = (): 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 { 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 { // 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 { false }; - let op = p.start(); - if p.eat(T![in]) { + 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![out]) { - parse_reg(p); - expr(p); - op.complete(p, ASM_REG_OPERAND); - } else if p.eat_contextual_kw(T![lateout]) { - 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 { } 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 { support::child(&self.syntax) } + pub fn path(&self) -> Option { support::child(&self.syntax) } #[inline] pub fn sym_token(&self) -> Option { 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 for AsmOperand { #[inline] fn from(node: AsmClobberAbi) -> AsmOperand { AsmOperand::AsmClobberAbi(node) } } +impl From for AsmOperand { + #[inline] + fn from(node: AsmConst) -> AsmOperand { AsmOperand::AsmConst(node) } +} impl From for AsmOperand { #[inline] fn from(node: AsmLabel) -> AsmOperand { AsmOperand::AsmLabel(node) } @@ -4603,18 +4609,27 @@ impl From for AsmOperand { #[inline] fn from(node: AsmRegOperand) -> AsmOperand { AsmOperand::AsmRegOperand(node) } } +impl From 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 { 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, } } }