]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_passes/src/intrinsicck.rs
Auto merge of #96348 - overdrivenpotato:inline-location, r=the8472
[rust.git] / compiler / rustc_passes / src / intrinsicck.rs
1 use hir::intravisit::walk_inline_asm;
2 use rustc_ast::InlineAsmTemplatePiece;
3 use rustc_data_structures::stable_set::FxHashSet;
4 use rustc_errors::struct_span_err;
5 use rustc_hir as hir;
6 use rustc_hir::def::{DefKind, Res};
7 use rustc_hir::def_id::{DefId, LocalDefId};
8 use rustc_hir::intravisit::{self, Visitor};
9 use rustc_index::vec::Idx;
10 use rustc_middle::ty::layout::{LayoutError, SizeSkeleton};
11 use rustc_middle::ty::query::Providers;
12 use rustc_middle::ty::{self, FloatTy, IntTy, Ty, TyCtxt, UintTy};
13 use rustc_session::lint;
14 use rustc_span::{sym, Span, Symbol, DUMMY_SP};
15 use rustc_target::abi::{Pointer, VariantIdx};
16 use rustc_target::asm::{InlineAsmRegOrRegClass, InlineAsmType};
17 use rustc_target::spec::abi::Abi::RustIntrinsic;
18
19 fn check_mod_intrinsics(tcx: TyCtxt<'_>, module_def_id: LocalDefId) {
20     tcx.hir().visit_item_likes_in_module(module_def_id, &mut ItemVisitor { tcx }.as_deep_visitor());
21 }
22
23 pub fn provide(providers: &mut Providers) {
24     *providers = Providers { check_mod_intrinsics, ..*providers };
25 }
26
27 struct ItemVisitor<'tcx> {
28     tcx: TyCtxt<'tcx>,
29 }
30
31 struct ExprVisitor<'tcx> {
32     tcx: TyCtxt<'tcx>,
33     typeck_results: &'tcx ty::TypeckResults<'tcx>,
34     param_env: ty::ParamEnv<'tcx>,
35 }
36
37 /// If the type is `Option<T>`, it will return `T`, otherwise
38 /// the type itself. Works on most `Option`-like types.
39 fn unpack_option_like<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
40     let ty::Adt(def, substs) = *ty.kind() else { return ty };
41
42     if def.variants().len() == 2 && !def.repr().c() && def.repr().int.is_none() {
43         let data_idx;
44
45         let one = VariantIdx::new(1);
46         let zero = VariantIdx::new(0);
47
48         if def.variant(zero).fields.is_empty() {
49             data_idx = one;
50         } else if def.variant(one).fields.is_empty() {
51             data_idx = zero;
52         } else {
53             return ty;
54         }
55
56         if def.variant(data_idx).fields.len() == 1 {
57             return def.variant(data_idx).fields[0].ty(tcx, substs);
58         }
59     }
60
61     ty
62 }
63
64 impl<'tcx> ExprVisitor<'tcx> {
65     fn def_id_is_transmute(&self, def_id: DefId) -> bool {
66         self.tcx.fn_sig(def_id).abi() == RustIntrinsic
67             && self.tcx.item_name(def_id) == sym::transmute
68     }
69
70     fn check_transmute(&self, span: Span, from: Ty<'tcx>, to: Ty<'tcx>) {
71         let sk_from = SizeSkeleton::compute(from, self.tcx, self.param_env);
72         let sk_to = SizeSkeleton::compute(to, self.tcx, self.param_env);
73
74         // Check for same size using the skeletons.
75         if let (Ok(sk_from), Ok(sk_to)) = (sk_from, sk_to) {
76             if sk_from.same_size(sk_to) {
77                 return;
78             }
79
80             // Special-case transmuting from `typeof(function)` and
81             // `Option<typeof(function)>` to present a clearer error.
82             let from = unpack_option_like(self.tcx, from);
83             if let (&ty::FnDef(..), SizeSkeleton::Known(size_to)) = (from.kind(), sk_to) && size_to == Pointer.size(&self.tcx) {
84                 struct_span_err!(self.tcx.sess, span, E0591, "can't transmute zero-sized type")
85                     .note(&format!("source type: {from}"))
86                     .note(&format!("target type: {to}"))
87                     .help("cast with `as` to a pointer instead")
88                     .emit();
89                 return;
90             }
91         }
92
93         // Try to display a sensible error with as much information as possible.
94         let skeleton_string = |ty: Ty<'tcx>, sk| match sk {
95             Ok(SizeSkeleton::Known(size)) => format!("{} bits", size.bits()),
96             Ok(SizeSkeleton::Pointer { tail, .. }) => format!("pointer to `{tail}`"),
97             Err(LayoutError::Unknown(bad)) => {
98                 if bad == ty {
99                     "this type does not have a fixed size".to_owned()
100                 } else {
101                     format!("size can vary because of {bad}")
102                 }
103             }
104             Err(err) => err.to_string(),
105         };
106
107         let mut err = struct_span_err!(
108             self.tcx.sess,
109             span,
110             E0512,
111             "cannot transmute between types of different sizes, \
112                                         or dependently-sized types"
113         );
114         if from == to {
115             err.note(&format!("`{from}` does not have a fixed size"));
116         } else {
117             err.note(&format!("source type: `{}` ({})", from, skeleton_string(from, sk_from)))
118                 .note(&format!("target type: `{}` ({})", to, skeleton_string(to, sk_to)));
119         }
120         err.emit();
121     }
122
123     fn is_thin_ptr_ty(&self, ty: Ty<'tcx>) -> bool {
124         if ty.is_sized(self.tcx.at(DUMMY_SP), self.param_env) {
125             return true;
126         }
127         if let ty::Foreign(..) = ty.kind() {
128             return true;
129         }
130         false
131     }
132
133     fn check_asm_operand_type(
134         &self,
135         idx: usize,
136         reg: InlineAsmRegOrRegClass,
137         expr: &hir::Expr<'tcx>,
138         template: &[InlineAsmTemplatePiece],
139         is_input: bool,
140         tied_input: Option<(&hir::Expr<'tcx>, Option<InlineAsmType>)>,
141         target_features: &FxHashSet<Symbol>,
142     ) -> Option<InlineAsmType> {
143         // Check the type against the allowed types for inline asm.
144         let ty = self.typeck_results.expr_ty_adjusted(expr);
145         let asm_ty_isize = match self.tcx.sess.target.pointer_width {
146             16 => InlineAsmType::I16,
147             32 => InlineAsmType::I32,
148             64 => InlineAsmType::I64,
149             _ => unreachable!(),
150         };
151         let asm_ty = match *ty.kind() {
152             // `!` is allowed for input but not for output (issue #87802)
153             ty::Never if is_input => return None,
154             ty::Error(_) => return None,
155             ty::Int(IntTy::I8) | ty::Uint(UintTy::U8) => Some(InlineAsmType::I8),
156             ty::Int(IntTy::I16) | ty::Uint(UintTy::U16) => Some(InlineAsmType::I16),
157             ty::Int(IntTy::I32) | ty::Uint(UintTy::U32) => Some(InlineAsmType::I32),
158             ty::Int(IntTy::I64) | ty::Uint(UintTy::U64) => Some(InlineAsmType::I64),
159             ty::Int(IntTy::I128) | ty::Uint(UintTy::U128) => Some(InlineAsmType::I128),
160             ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize) => Some(asm_ty_isize),
161             ty::Float(FloatTy::F32) => Some(InlineAsmType::F32),
162             ty::Float(FloatTy::F64) => Some(InlineAsmType::F64),
163             ty::FnPtr(_) => Some(asm_ty_isize),
164             ty::RawPtr(ty::TypeAndMut { ty, mutbl: _ }) if self.is_thin_ptr_ty(ty) => {
165                 Some(asm_ty_isize)
166             }
167             ty::Adt(adt, substs) if adt.repr().simd() => {
168                 let fields = &adt.non_enum_variant().fields;
169                 let elem_ty = fields[0].ty(self.tcx, substs);
170                 match elem_ty.kind() {
171                     ty::Never | ty::Error(_) => return None,
172                     ty::Int(IntTy::I8) | ty::Uint(UintTy::U8) => {
173                         Some(InlineAsmType::VecI8(fields.len() as u64))
174                     }
175                     ty::Int(IntTy::I16) | ty::Uint(UintTy::U16) => {
176                         Some(InlineAsmType::VecI16(fields.len() as u64))
177                     }
178                     ty::Int(IntTy::I32) | ty::Uint(UintTy::U32) => {
179                         Some(InlineAsmType::VecI32(fields.len() as u64))
180                     }
181                     ty::Int(IntTy::I64) | ty::Uint(UintTy::U64) => {
182                         Some(InlineAsmType::VecI64(fields.len() as u64))
183                     }
184                     ty::Int(IntTy::I128) | ty::Uint(UintTy::U128) => {
185                         Some(InlineAsmType::VecI128(fields.len() as u64))
186                     }
187                     ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize) => {
188                         Some(match self.tcx.sess.target.pointer_width {
189                             16 => InlineAsmType::VecI16(fields.len() as u64),
190                             32 => InlineAsmType::VecI32(fields.len() as u64),
191                             64 => InlineAsmType::VecI64(fields.len() as u64),
192                             _ => unreachable!(),
193                         })
194                     }
195                     ty::Float(FloatTy::F32) => Some(InlineAsmType::VecF32(fields.len() as u64)),
196                     ty::Float(FloatTy::F64) => Some(InlineAsmType::VecF64(fields.len() as u64)),
197                     _ => None,
198                 }
199             }
200             _ => None,
201         };
202         let Some(asm_ty) = asm_ty else {
203             let msg = &format!("cannot use value of type `{ty}` for inline assembly");
204             let mut err = self.tcx.sess.struct_span_err(expr.span, msg);
205             err.note(
206                 "only integers, floats, SIMD vectors, pointers and function pointers \
207                  can be used as arguments for inline assembly",
208             );
209             err.emit();
210             return None;
211         };
212
213         // Check that the type implements Copy. The only case where this can
214         // possibly fail is for SIMD types which don't #[derive(Copy)].
215         if !ty.is_copy_modulo_regions(self.tcx.at(DUMMY_SP), self.param_env) {
216             let msg = "arguments for inline assembly must be copyable";
217             let mut err = self.tcx.sess.struct_span_err(expr.span, msg);
218             err.note(&format!("`{ty}` does not implement the Copy trait"));
219             err.emit();
220         }
221
222         // Ideally we wouldn't need to do this, but LLVM's register allocator
223         // really doesn't like it when tied operands have different types.
224         //
225         // This is purely an LLVM limitation, but we have to live with it since
226         // there is no way to hide this with implicit conversions.
227         //
228         // For the purposes of this check we only look at the `InlineAsmType`,
229         // which means that pointers and integers are treated as identical (modulo
230         // size).
231         if let Some((in_expr, Some(in_asm_ty))) = tied_input {
232             if in_asm_ty != asm_ty {
233                 let msg = "incompatible types for asm inout argument";
234                 let mut err = self.tcx.sess.struct_span_err(vec![in_expr.span, expr.span], msg);
235                 err.span_label(
236                     in_expr.span,
237                     &format!("type `{}`", self.typeck_results.expr_ty_adjusted(in_expr)),
238                 );
239                 err.span_label(expr.span, &format!("type `{ty}`"));
240                 err.note(
241                     "asm inout arguments must have the same type, \
242                     unless they are both pointers or integers of the same size",
243                 );
244                 err.emit();
245             }
246
247             // All of the later checks have already been done on the input, so
248             // let's not emit errors and warnings twice.
249             return Some(asm_ty);
250         }
251
252         // Check the type against the list of types supported by the selected
253         // register class.
254         let asm_arch = self.tcx.sess.asm_arch.unwrap();
255         let reg_class = reg.reg_class();
256         let supported_tys = reg_class.supported_types(asm_arch);
257         let Some((_, feature)) = supported_tys.iter().find(|&&(t, _)| t == asm_ty) else {
258             let msg = &format!("type `{ty}` cannot be used with this register class");
259             let mut err = self.tcx.sess.struct_span_err(expr.span, msg);
260             let supported_tys: Vec<_> =
261                 supported_tys.iter().map(|(t, _)| t.to_string()).collect();
262             err.note(&format!(
263                 "register class `{}` supports these types: {}",
264                 reg_class.name(),
265                 supported_tys.join(", "),
266             ));
267             if let Some(suggest) = reg_class.suggest_class(asm_arch, asm_ty) {
268                 err.help(&format!(
269                     "consider using the `{}` register class instead",
270                     suggest.name()
271                 ));
272             }
273             err.emit();
274             return Some(asm_ty);
275         };
276
277         // Check whether the selected type requires a target feature. Note that
278         // this is different from the feature check we did earlier. While the
279         // previous check checked that this register class is usable at all
280         // with the currently enabled features, some types may only be usable
281         // with a register class when a certain feature is enabled. We check
282         // this here since it depends on the results of typeck.
283         //
284         // Also note that this check isn't run when the operand type is never
285         // (!). In that case we still need the earlier check to verify that the
286         // register class is usable at all.
287         if let Some(feature) = feature {
288             if !target_features.contains(&feature) {
289                 let msg = &format!("`{}` target feature is not enabled", feature);
290                 let mut err = self.tcx.sess.struct_span_err(expr.span, msg);
291                 err.note(&format!(
292                     "this is required to use type `{}` with register class `{}`",
293                     ty,
294                     reg_class.name(),
295                 ));
296                 err.emit();
297                 return Some(asm_ty);
298             }
299         }
300
301         // Check whether a modifier is suggested for using this type.
302         if let Some((suggested_modifier, suggested_result)) =
303             reg_class.suggest_modifier(asm_arch, asm_ty)
304         {
305             // Search for any use of this operand without a modifier and emit
306             // the suggestion for them.
307             let mut spans = vec![];
308             for piece in template {
309                 if let &InlineAsmTemplatePiece::Placeholder { operand_idx, modifier, span } = piece
310                 {
311                     if operand_idx == idx && modifier.is_none() {
312                         spans.push(span);
313                     }
314                 }
315             }
316             if !spans.is_empty() {
317                 let (default_modifier, default_result) =
318                     reg_class.default_modifier(asm_arch).unwrap();
319                 self.tcx.struct_span_lint_hir(
320                     lint::builtin::ASM_SUB_REGISTER,
321                     expr.hir_id,
322                     spans,
323                     |lint| {
324                         let msg = "formatting may not be suitable for sub-register argument";
325                         let mut err = lint.build(msg);
326                         err.span_label(expr.span, "for this argument");
327                         err.help(&format!(
328                             "use the `{suggested_modifier}` modifier to have the register formatted as `{suggested_result}`",
329                         ));
330                         err.help(&format!(
331                             "or use the `{default_modifier}` modifier to keep the default formatting of `{default_result}`",
332                         ));
333                         err.emit();
334                     },
335                 );
336             }
337         }
338
339         Some(asm_ty)
340     }
341
342     fn check_asm(&self, asm: &hir::InlineAsm<'tcx>, hir_id: hir::HirId) {
343         let hir = self.tcx.hir();
344         let enclosing_id = hir.enclosing_body_owner(hir_id);
345         let enclosing_def_id = hir.local_def_id(enclosing_id).to_def_id();
346         let target_features = self.tcx.asm_target_features(enclosing_def_id);
347         let asm_arch = self.tcx.sess.asm_arch.unwrap();
348         for (idx, (op, op_sp)) in asm.operands.iter().enumerate() {
349             // Validate register classes against currently enabled target
350             // features. We check that at least one type is available for
351             // the enabled features.
352             //
353             // We ignore target feature requirements for clobbers: if the
354             // feature is disabled then the compiler doesn't care what we
355             // do with the registers.
356             //
357             // Note that this is only possible for explicit register
358             // operands, which cannot be used in the asm string.
359             if let Some(reg) = op.reg() {
360                 // Some explicit registers cannot be used depending on the
361                 // target. Reject those here.
362                 if let InlineAsmRegOrRegClass::Reg(reg) = reg {
363                     if let Err(msg) = reg.validate(
364                         asm_arch,
365                         self.tcx.sess.relocation_model(),
366                         &target_features,
367                         &self.tcx.sess.target,
368                         op.is_clobber(),
369                     ) {
370                         let msg = format!("cannot use register `{}`: {}", reg.name(), msg);
371                         self.tcx.sess.struct_span_err(*op_sp, &msg).emit();
372                         continue;
373                     }
374                 }
375
376                 if !op.is_clobber() {
377                     let mut missing_required_features = vec![];
378                     let reg_class = reg.reg_class();
379                     for &(_, feature) in reg_class.supported_types(asm_arch) {
380                         match feature {
381                             Some(feature) => {
382                                 if target_features.contains(&feature) {
383                                     missing_required_features.clear();
384                                     break;
385                                 } else {
386                                     missing_required_features.push(feature);
387                                 }
388                             }
389                             None => {
390                                 missing_required_features.clear();
391                                 break;
392                             }
393                         }
394                     }
395
396                     // We are sorting primitive strs here and can use unstable sort here
397                     missing_required_features.sort_unstable();
398                     missing_required_features.dedup();
399                     match &missing_required_features[..] {
400                         [] => {}
401                         [feature] => {
402                             let msg = format!(
403                                 "register class `{}` requires the `{}` target feature",
404                                 reg_class.name(),
405                                 feature
406                             );
407                             self.tcx.sess.struct_span_err(*op_sp, &msg).emit();
408                             // register isn't enabled, don't do more checks
409                             continue;
410                         }
411                         features => {
412                             let msg = format!(
413                                 "register class `{}` requires at least one of the following target features: {}",
414                                 reg_class.name(),
415                                 features
416                                     .iter()
417                                     .map(|f| f.as_str())
418                                     .intersperse(", ")
419                                     .collect::<String>(),
420                             );
421                             self.tcx.sess.struct_span_err(*op_sp, &msg).emit();
422                             // register isn't enabled, don't do more checks
423                             continue;
424                         }
425                     }
426                 }
427             }
428
429             match *op {
430                 hir::InlineAsmOperand::In { reg, ref expr } => {
431                     self.check_asm_operand_type(
432                         idx,
433                         reg,
434                         expr,
435                         asm.template,
436                         true,
437                         None,
438                         &target_features,
439                     );
440                 }
441                 hir::InlineAsmOperand::Out { reg, late: _, ref expr } => {
442                     if let Some(expr) = expr {
443                         self.check_asm_operand_type(
444                             idx,
445                             reg,
446                             expr,
447                             asm.template,
448                             false,
449                             None,
450                             &target_features,
451                         );
452                     }
453                 }
454                 hir::InlineAsmOperand::InOut { reg, late: _, ref expr } => {
455                     self.check_asm_operand_type(
456                         idx,
457                         reg,
458                         expr,
459                         asm.template,
460                         false,
461                         None,
462                         &target_features,
463                     );
464                 }
465                 hir::InlineAsmOperand::SplitInOut { reg, late: _, ref in_expr, ref out_expr } => {
466                     let in_ty = self.check_asm_operand_type(
467                         idx,
468                         reg,
469                         in_expr,
470                         asm.template,
471                         true,
472                         None,
473                         &target_features,
474                     );
475                     if let Some(out_expr) = out_expr {
476                         self.check_asm_operand_type(
477                             idx,
478                             reg,
479                             out_expr,
480                             asm.template,
481                             false,
482                             Some((in_expr, in_ty)),
483                             &target_features,
484                         );
485                     }
486                 }
487                 // These are checked in ItemVisitor.
488                 hir::InlineAsmOperand::Const { .. }
489                 | hir::InlineAsmOperand::SymFn { .. }
490                 | hir::InlineAsmOperand::SymStatic { .. } => {}
491             }
492         }
493     }
494 }
495
496 impl<'tcx> Visitor<'tcx> for ItemVisitor<'tcx> {
497     fn visit_nested_body(&mut self, body_id: hir::BodyId) {
498         let owner_def_id = self.tcx.hir().body_owner_def_id(body_id);
499         let body = self.tcx.hir().body(body_id);
500         let param_env = self.tcx.param_env(owner_def_id.to_def_id());
501         let typeck_results = self.tcx.typeck(owner_def_id);
502         ExprVisitor { tcx: self.tcx, param_env, typeck_results }.visit_body(body);
503         self.visit_body(body);
504     }
505
506     fn visit_inline_asm(&mut self, asm: &'tcx hir::InlineAsm<'tcx>, id: hir::HirId) {
507         for (op, op_sp) in asm.operands.iter() {
508             match *op {
509                 // These are checked in ExprVisitor.
510                 hir::InlineAsmOperand::In { .. }
511                 | hir::InlineAsmOperand::Out { .. }
512                 | hir::InlineAsmOperand::InOut { .. }
513                 | hir::InlineAsmOperand::SplitInOut { .. } => {}
514                 // No special checking is needed for these:
515                 // - Typeck has checked that Const operands are integers.
516                 // - AST lowering guarantees that SymStatic points to a static.
517                 hir::InlineAsmOperand::Const { .. } | hir::InlineAsmOperand::SymStatic { .. } => {}
518                 // Check that sym actually points to a function. Later passes
519                 // depend on this.
520                 hir::InlineAsmOperand::SymFn { anon_const } => {
521                     let ty = self.tcx.typeck_body(anon_const.body).node_type(anon_const.hir_id);
522                     match ty.kind() {
523                         ty::Never | ty::Error(_) => {}
524                         ty::FnDef(..) => {}
525                         _ => {
526                             let mut err =
527                                 self.tcx.sess.struct_span_err(*op_sp, "invalid `sym` operand");
528                             err.span_label(
529                                 self.tcx.hir().span(anon_const.body.hir_id),
530                                 &format!("is {} `{}`", ty.kind().article(), ty),
531                             );
532                             err.help("`sym` operands must refer to either a function or a static");
533                             err.emit();
534                         }
535                     };
536                 }
537             }
538         }
539         walk_inline_asm(self, asm, id);
540     }
541 }
542
543 impl<'tcx> Visitor<'tcx> for ExprVisitor<'tcx> {
544     fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
545         match expr.kind {
546             hir::ExprKind::Path(ref qpath) => {
547                 let res = self.typeck_results.qpath_res(qpath, expr.hir_id);
548                 if let Res::Def(DefKind::Fn, did) = res
549                     && self.def_id_is_transmute(did)
550                 {
551                     let typ = self.typeck_results.node_type(expr.hir_id);
552                     let sig = typ.fn_sig(self.tcx);
553                     let from = sig.inputs().skip_binder()[0];
554                     let to = sig.output().skip_binder();
555                     self.check_transmute(expr.span, from, to);
556                 }
557             }
558
559             hir::ExprKind::InlineAsm(asm) => self.check_asm(asm, expr.hir_id),
560
561             _ => {}
562         }
563
564         intravisit::walk_expr(self, expr);
565     }
566 }