]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_ast_lowering/src/asm.rs
Rollup merge of #98200 - ouz-a:issue-98177, r=oli-obk
[rust.git] / compiler / rustc_ast_lowering / src / asm.rs
1 use crate::{ImplTraitContext, ImplTraitPosition, ParamMode, ResolverAstLoweringExt};
2
3 use super::LoweringContext;
4
5 use rustc_ast::ptr::P;
6 use rustc_ast::*;
7 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
8 use rustc_errors::struct_span_err;
9 use rustc_hir as hir;
10 use rustc_hir::def::{DefKind, Res};
11 use rustc_hir::definitions::DefPathData;
12 use rustc_session::parse::feature_err;
13 use rustc_span::{sym, Span};
14 use rustc_target::asm;
15 use std::collections::hash_map::Entry;
16 use std::fmt::Write;
17
18 impl<'a, 'hir> LoweringContext<'a, 'hir> {
19     pub(crate) fn lower_inline_asm(
20         &mut self,
21         sp: Span,
22         asm: &InlineAsm,
23     ) -> &'hir hir::InlineAsm<'hir> {
24         // Rustdoc needs to support asm! from foreign architectures: don't try
25         // lowering the register constraints in this case.
26         let asm_arch =
27             if self.tcx.sess.opts.actually_rustdoc { None } else { self.tcx.sess.asm_arch };
28         if asm_arch.is_none() && !self.tcx.sess.opts.actually_rustdoc {
29             struct_span_err!(
30                 self.tcx.sess,
31                 sp,
32                 E0472,
33                 "inline assembly is unsupported on this target"
34             )
35             .emit();
36         }
37         if let Some(asm_arch) = asm_arch {
38             // Inline assembly is currently only stable for these architectures.
39             let is_stable = matches!(
40                 asm_arch,
41                 asm::InlineAsmArch::X86
42                     | asm::InlineAsmArch::X86_64
43                     | asm::InlineAsmArch::Arm
44                     | asm::InlineAsmArch::AArch64
45                     | asm::InlineAsmArch::RiscV32
46                     | asm::InlineAsmArch::RiscV64
47             );
48             if !is_stable && !self.tcx.features().asm_experimental_arch {
49                 feature_err(
50                     &self.tcx.sess.parse_sess,
51                     sym::asm_experimental_arch,
52                     sp,
53                     "inline assembly is not stable yet on this architecture",
54                 )
55                 .emit();
56             }
57         }
58         if asm.options.contains(InlineAsmOptions::ATT_SYNTAX)
59             && !matches!(asm_arch, Some(asm::InlineAsmArch::X86 | asm::InlineAsmArch::X86_64))
60             && !self.tcx.sess.opts.actually_rustdoc
61         {
62             self.tcx
63                 .sess
64                 .struct_span_err(sp, "the `att_syntax` option is only supported on x86")
65                 .emit();
66         }
67         if asm.options.contains(InlineAsmOptions::MAY_UNWIND) && !self.tcx.features().asm_unwind {
68             feature_err(
69                 &self.tcx.sess.parse_sess,
70                 sym::asm_unwind,
71                 sp,
72                 "the `may_unwind` option is unstable",
73             )
74             .emit();
75         }
76
77         let mut clobber_abis = FxHashMap::default();
78         if let Some(asm_arch) = asm_arch {
79             for (abi_name, abi_span) in &asm.clobber_abis {
80                 match asm::InlineAsmClobberAbi::parse(asm_arch, &self.tcx.sess.target, *abi_name) {
81                     Ok(abi) => {
82                         // If the abi was already in the list, emit an error
83                         match clobber_abis.get(&abi) {
84                             Some((prev_name, prev_sp)) => {
85                                 let mut err = self.tcx.sess.struct_span_err(
86                                     *abi_span,
87                                     &format!("`{}` ABI specified multiple times", prev_name),
88                                 );
89                                 err.span_label(*prev_sp, "previously specified here");
90
91                                 // Multiple different abi names may actually be the same ABI
92                                 // If the specified ABIs are not the same name, alert the user that they resolve to the same ABI
93                                 let source_map = self.tcx.sess.source_map();
94                                 if source_map.span_to_snippet(*prev_sp)
95                                     != source_map.span_to_snippet(*abi_span)
96                                 {
97                                     err.note("these ABIs are equivalent on the current target");
98                                 }
99
100                                 err.emit();
101                             }
102                             None => {
103                                 clobber_abis.insert(abi, (abi_name, *abi_span));
104                             }
105                         }
106                     }
107                     Err(&[]) => {
108                         self.tcx
109                             .sess
110                             .struct_span_err(
111                                 *abi_span,
112                                 "`clobber_abi` is not supported on this target",
113                             )
114                             .emit();
115                     }
116                     Err(supported_abis) => {
117                         let mut err = self
118                             .tcx
119                             .sess
120                             .struct_span_err(*abi_span, "invalid ABI for `clobber_abi`");
121                         let mut abis = format!("`{}`", supported_abis[0]);
122                         for m in &supported_abis[1..] {
123                             let _ = write!(abis, ", `{}`", m);
124                         }
125                         err.note(&format!(
126                             "the following ABIs are supported on this target: {}",
127                             abis
128                         ));
129                         err.emit();
130                     }
131                 }
132             }
133         }
134
135         // Lower operands to HIR. We use dummy register classes if an error
136         // occurs during lowering because we still need to be able to produce a
137         // valid HIR.
138         let sess = self.tcx.sess;
139         let mut operands: Vec<_> = asm
140             .operands
141             .iter()
142             .map(|(op, op_sp)| {
143                 let lower_reg = |reg| match reg {
144                     InlineAsmRegOrRegClass::Reg(s) => {
145                         asm::InlineAsmRegOrRegClass::Reg(if let Some(asm_arch) = asm_arch {
146                             asm::InlineAsmReg::parse(asm_arch, s).unwrap_or_else(|e| {
147                                 let msg = format!("invalid register `{}`: {}", s, e);
148                                 sess.struct_span_err(*op_sp, &msg).emit();
149                                 asm::InlineAsmReg::Err
150                             })
151                         } else {
152                             asm::InlineAsmReg::Err
153                         })
154                     }
155                     InlineAsmRegOrRegClass::RegClass(s) => {
156                         asm::InlineAsmRegOrRegClass::RegClass(if let Some(asm_arch) = asm_arch {
157                             asm::InlineAsmRegClass::parse(asm_arch, s).unwrap_or_else(|e| {
158                                 let msg = format!("invalid register class `{}`: {}", s, e);
159                                 sess.struct_span_err(*op_sp, &msg).emit();
160                                 asm::InlineAsmRegClass::Err
161                             })
162                         } else {
163                             asm::InlineAsmRegClass::Err
164                         })
165                     }
166                 };
167
168                 let op = match *op {
169                     InlineAsmOperand::In { reg, ref expr } => hir::InlineAsmOperand::In {
170                         reg: lower_reg(reg),
171                         expr: self.lower_expr_mut(expr),
172                     },
173                     InlineAsmOperand::Out { reg, late, ref expr } => hir::InlineAsmOperand::Out {
174                         reg: lower_reg(reg),
175                         late,
176                         expr: expr.as_ref().map(|expr| self.lower_expr_mut(expr)),
177                     },
178                     InlineAsmOperand::InOut { reg, late, ref expr } => {
179                         hir::InlineAsmOperand::InOut {
180                             reg: lower_reg(reg),
181                             late,
182                             expr: self.lower_expr_mut(expr),
183                         }
184                     }
185                     InlineAsmOperand::SplitInOut { reg, late, ref in_expr, ref out_expr } => {
186                         hir::InlineAsmOperand::SplitInOut {
187                             reg: lower_reg(reg),
188                             late,
189                             in_expr: self.lower_expr_mut(in_expr),
190                             out_expr: out_expr.as_ref().map(|expr| self.lower_expr_mut(expr)),
191                         }
192                     }
193                     InlineAsmOperand::Const { ref anon_const } => {
194                         if !self.tcx.features().asm_const {
195                             feature_err(
196                                 &sess.parse_sess,
197                                 sym::asm_const,
198                                 *op_sp,
199                                 "const operands for inline assembly are unstable",
200                             )
201                             .emit();
202                         }
203                         hir::InlineAsmOperand::Const {
204                             anon_const: self.lower_anon_const(anon_const),
205                         }
206                     }
207                     InlineAsmOperand::Sym { ref sym } => {
208                         if !self.tcx.features().asm_sym {
209                             feature_err(
210                                 &sess.parse_sess,
211                                 sym::asm_sym,
212                                 *op_sp,
213                                 "sym operands for inline assembly are unstable",
214                             )
215                             .emit();
216                         }
217
218                         let static_def_id = self
219                             .resolver
220                             .get_partial_res(sym.id)
221                             .filter(|res| res.unresolved_segments() == 0)
222                             .and_then(|res| {
223                                 if let Res::Def(DefKind::Static(_), def_id) = res.base_res() {
224                                     Some(def_id)
225                                 } else {
226                                     None
227                                 }
228                             });
229
230                         if let Some(def_id) = static_def_id {
231                             let path = self.lower_qpath(
232                                 sym.id,
233                                 &sym.qself,
234                                 &sym.path,
235                                 ParamMode::Optional,
236                                 ImplTraitContext::Disallowed(ImplTraitPosition::Path),
237                             );
238                             hir::InlineAsmOperand::SymStatic { path, def_id }
239                         } else {
240                             // Replace the InlineAsmSym AST node with an
241                             // Expr using the name node id.
242                             let expr = Expr {
243                                 id: sym.id,
244                                 kind: ExprKind::Path(sym.qself.clone(), sym.path.clone()),
245                                 span: *op_sp,
246                                 attrs: AttrVec::new(),
247                                 tokens: None,
248                             };
249
250                             // Wrap the expression in an AnonConst.
251                             let parent_def_id = self.current_hir_id_owner;
252                             let node_id = self.next_node_id();
253                             self.create_def(parent_def_id, node_id, DefPathData::AnonConst);
254                             let anon_const = AnonConst { id: node_id, value: P(expr) };
255                             hir::InlineAsmOperand::SymFn {
256                                 anon_const: self.lower_anon_const(&anon_const),
257                             }
258                         }
259                     }
260                 };
261                 (op, self.lower_span(*op_sp))
262             })
263             .collect();
264
265         // Validate template modifiers against the register classes for the operands
266         for p in &asm.template {
267             if let InlineAsmTemplatePiece::Placeholder {
268                 operand_idx,
269                 modifier: Some(modifier),
270                 span: placeholder_span,
271             } = *p
272             {
273                 let op_sp = asm.operands[operand_idx].1;
274                 match &operands[operand_idx].0 {
275                     hir::InlineAsmOperand::In { reg, .. }
276                     | hir::InlineAsmOperand::Out { reg, .. }
277                     | hir::InlineAsmOperand::InOut { reg, .. }
278                     | hir::InlineAsmOperand::SplitInOut { reg, .. } => {
279                         let class = reg.reg_class();
280                         if class == asm::InlineAsmRegClass::Err {
281                             continue;
282                         }
283                         let valid_modifiers = class.valid_modifiers(asm_arch.unwrap());
284                         if !valid_modifiers.contains(&modifier) {
285                             let mut err = sess.struct_span_err(
286                                 placeholder_span,
287                                 "invalid asm template modifier for this register class",
288                             );
289                             err.span_label(placeholder_span, "template modifier");
290                             err.span_label(op_sp, "argument");
291                             if !valid_modifiers.is_empty() {
292                                 let mut mods = format!("`{}`", valid_modifiers[0]);
293                                 for m in &valid_modifiers[1..] {
294                                     let _ = write!(mods, ", `{}`", m);
295                                 }
296                                 err.note(&format!(
297                                     "the `{}` register class supports \
298                                      the following template modifiers: {}",
299                                     class.name(),
300                                     mods
301                                 ));
302                             } else {
303                                 err.note(&format!(
304                                     "the `{}` register class does not support template modifiers",
305                                     class.name()
306                                 ));
307                             }
308                             err.emit();
309                         }
310                     }
311                     hir::InlineAsmOperand::Const { .. } => {
312                         let mut err = sess.struct_span_err(
313                             placeholder_span,
314                             "asm template modifiers are not allowed for `const` arguments",
315                         );
316                         err.span_label(placeholder_span, "template modifier");
317                         err.span_label(op_sp, "argument");
318                         err.emit();
319                     }
320                     hir::InlineAsmOperand::SymFn { .. }
321                     | hir::InlineAsmOperand::SymStatic { .. } => {
322                         let mut err = sess.struct_span_err(
323                             placeholder_span,
324                             "asm template modifiers are not allowed for `sym` arguments",
325                         );
326                         err.span_label(placeholder_span, "template modifier");
327                         err.span_label(op_sp, "argument");
328                         err.emit();
329                     }
330                 }
331             }
332         }
333
334         let mut used_input_regs = FxHashMap::default();
335         let mut used_output_regs = FxHashMap::default();
336
337         for (idx, &(ref op, op_sp)) in operands.iter().enumerate() {
338             if let Some(reg) = op.reg() {
339                 let reg_class = reg.reg_class();
340                 if reg_class == asm::InlineAsmRegClass::Err {
341                     continue;
342                 }
343
344                 // Some register classes can only be used as clobbers. This
345                 // means that we disallow passing a value in/out of the asm and
346                 // require that the operand name an explicit register, not a
347                 // register class.
348                 if reg_class.is_clobber_only(asm_arch.unwrap()) && !op.is_clobber() {
349                     let msg = format!(
350                         "register class `{}` can only be used as a clobber, \
351                              not as an input or output",
352                         reg_class.name()
353                     );
354                     sess.struct_span_err(op_sp, &msg).emit();
355                     continue;
356                 }
357
358                 // Check for conflicts between explicit register operands.
359                 if let asm::InlineAsmRegOrRegClass::Reg(reg) = reg {
360                     let (input, output) = match op {
361                         hir::InlineAsmOperand::In { .. } => (true, false),
362
363                         // Late output do not conflict with inputs, but normal outputs do
364                         hir::InlineAsmOperand::Out { late, .. } => (!late, true),
365
366                         hir::InlineAsmOperand::InOut { .. }
367                         | hir::InlineAsmOperand::SplitInOut { .. } => (true, true),
368
369                         hir::InlineAsmOperand::Const { .. }
370                         | hir::InlineAsmOperand::SymFn { .. }
371                         | hir::InlineAsmOperand::SymStatic { .. } => {
372                             unreachable!()
373                         }
374                     };
375
376                     // Flag to output the error only once per operand
377                     let mut skip = false;
378                     reg.overlapping_regs(|r| {
379                         let mut check = |used_regs: &mut FxHashMap<asm::InlineAsmReg, usize>,
380                                          input| {
381                             match used_regs.entry(r) {
382                                 Entry::Occupied(o) => {
383                                     if skip {
384                                         return;
385                                     }
386                                     skip = true;
387
388                                     let idx2 = *o.get();
389                                     let &(ref op2, op_sp2) = &operands[idx2];
390                                     let Some(asm::InlineAsmRegOrRegClass::Reg(reg2)) = op2.reg() else {
391                                         unreachable!();
392                                     };
393
394                                     let msg = format!(
395                                         "register `{}` conflicts with register `{}`",
396                                         reg.name(),
397                                         reg2.name()
398                                     );
399                                     let mut err = sess.struct_span_err(op_sp, &msg);
400                                     err.span_label(op_sp, &format!("register `{}`", reg.name()));
401                                     err.span_label(op_sp2, &format!("register `{}`", reg2.name()));
402
403                                     match (op, op2) {
404                                         (
405                                             hir::InlineAsmOperand::In { .. },
406                                             hir::InlineAsmOperand::Out { late, .. },
407                                         )
408                                         | (
409                                             hir::InlineAsmOperand::Out { late, .. },
410                                             hir::InlineAsmOperand::In { .. },
411                                         ) => {
412                                             assert!(!*late);
413                                             let out_op_sp = if input { op_sp2 } else { op_sp };
414                                             let msg = "use `lateout` instead of \
415                                                        `out` to avoid conflict";
416                                             err.span_help(out_op_sp, msg);
417                                         }
418                                         _ => {}
419                                     }
420
421                                     err.emit();
422                                 }
423                                 Entry::Vacant(v) => {
424                                     if r == reg {
425                                         v.insert(idx);
426                                     }
427                                 }
428                             }
429                         };
430                         if input {
431                             check(&mut used_input_regs, true);
432                         }
433                         if output {
434                             check(&mut used_output_regs, false);
435                         }
436                     });
437                 }
438             }
439         }
440
441         // If a clobber_abi is specified, add the necessary clobbers to the
442         // operands list.
443         let mut clobbered = FxHashSet::default();
444         for (abi, (_, abi_span)) in clobber_abis {
445             for &clobber in abi.clobbered_regs() {
446                 // Don't emit a clobber for a register already clobbered
447                 if clobbered.contains(&clobber) {
448                     continue;
449                 }
450
451                 let mut output_used = false;
452                 clobber.overlapping_regs(|reg| {
453                     if used_output_regs.contains_key(&reg) {
454                         output_used = true;
455                     }
456                 });
457
458                 if !output_used {
459                     operands.push((
460                         hir::InlineAsmOperand::Out {
461                             reg: asm::InlineAsmRegOrRegClass::Reg(clobber),
462                             late: true,
463                             expr: None,
464                         },
465                         self.lower_span(abi_span),
466                     ));
467                     clobbered.insert(clobber);
468                 }
469             }
470         }
471
472         let operands = self.arena.alloc_from_iter(operands);
473         let template = self.arena.alloc_from_iter(asm.template.iter().cloned());
474         let template_strs = self.arena.alloc_from_iter(
475             asm.template_strs
476                 .iter()
477                 .map(|(sym, snippet, span)| (*sym, *snippet, self.lower_span(*span))),
478         );
479         let line_spans =
480             self.arena.alloc_from_iter(asm.line_spans.iter().map(|span| self.lower_span(*span)));
481         let hir_asm =
482             hir::InlineAsm { template, template_strs, operands, options: asm.options, line_spans };
483         self.arena.alloc(hir_asm)
484     }
485 }