rustc_hir_analysis/check/
intrinsicck.rs

1use rustc_abi::FieldIdx;
2use rustc_ast::InlineAsmTemplatePiece;
3use rustc_data_structures::fx::FxIndexSet;
4use rustc_hir::def_id::DefId;
5use rustc_hir::{self as hir, LangItem};
6use rustc_infer::infer::InferCtxt;
7use rustc_middle::bug;
8use rustc_middle::ty::{
9    self, Article, FloatTy, IntTy, Ty, TyCtxt, TypeVisitableExt, TypeckResults, UintTy,
10};
11use rustc_session::lint;
12use rustc_span::def_id::LocalDefId;
13use rustc_span::{Symbol, sym};
14use rustc_target::asm::{
15    InlineAsmReg, InlineAsmRegClass, InlineAsmRegOrRegClass, InlineAsmType, ModifierInfo,
16};
17
18use crate::errors::RegisterTypeUnstable;
19
20pub struct InlineAsmCtxt<'a, 'tcx> {
21    typing_env: ty::TypingEnv<'tcx>,
22    target_features: &'tcx FxIndexSet<Symbol>,
23    infcx: &'a InferCtxt<'tcx>,
24    typeck_results: &'a TypeckResults<'tcx>,
25}
26
27enum NonAsmTypeReason<'tcx> {
28    UnevaluatedSIMDArrayLength(DefId, ty::Const<'tcx>),
29    Invalid(Ty<'tcx>),
30    InvalidElement(DefId, Ty<'tcx>),
31    NotSizedPtr(Ty<'tcx>),
32}
33
34impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> {
35    pub fn new(
36        def_id: LocalDefId,
37        infcx: &'a InferCtxt<'tcx>,
38        typing_env: ty::TypingEnv<'tcx>,
39        typeck_results: &'a TypeckResults<'tcx>,
40    ) -> Self {
41        InlineAsmCtxt {
42            typing_env,
43            target_features: infcx.tcx.asm_target_features(def_id),
44            infcx,
45            typeck_results,
46        }
47    }
48
49    fn tcx(&self) -> TyCtxt<'tcx> {
50        self.infcx.tcx
51    }
52
53    fn expr_ty(&self, expr: &hir::Expr<'tcx>) -> Ty<'tcx> {
54        let ty = self.typeck_results.expr_ty_adjusted(expr);
55        let ty = self.infcx.resolve_vars_if_possible(ty);
56        if ty.has_non_region_infer() {
57            Ty::new_misc_error(self.tcx())
58        } else {
59            self.tcx().erase_regions(ty)
60        }
61    }
62
63    // FIXME(compiler-errors): This could use `<$ty as Pointee>::Metadata == ()`
64    fn is_thin_ptr_ty(&self, ty: Ty<'tcx>) -> bool {
65        // Type still may have region variables, but `Sized` does not depend
66        // on those, so just erase them before querying.
67        if ty.is_sized(self.tcx(), self.typing_env) {
68            return true;
69        }
70        if let ty::Foreign(..) = ty.kind() {
71            return true;
72        }
73        false
74    }
75
76    fn get_asm_ty(&self, ty: Ty<'tcx>) -> Result<InlineAsmType, NonAsmTypeReason<'tcx>> {
77        let asm_ty_isize = match self.tcx().sess.target.pointer_width {
78            16 => InlineAsmType::I16,
79            32 => InlineAsmType::I32,
80            64 => InlineAsmType::I64,
81            width => bug!("unsupported pointer width: {width}"),
82        };
83
84        match *ty.kind() {
85            ty::Int(IntTy::I8) | ty::Uint(UintTy::U8) => Ok(InlineAsmType::I8),
86            ty::Int(IntTy::I16) | ty::Uint(UintTy::U16) => Ok(InlineAsmType::I16),
87            ty::Int(IntTy::I32) | ty::Uint(UintTy::U32) => Ok(InlineAsmType::I32),
88            ty::Int(IntTy::I64) | ty::Uint(UintTy::U64) => Ok(InlineAsmType::I64),
89            ty::Int(IntTy::I128) | ty::Uint(UintTy::U128) => Ok(InlineAsmType::I128),
90            ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize) => Ok(asm_ty_isize),
91            ty::Float(FloatTy::F16) => Ok(InlineAsmType::F16),
92            ty::Float(FloatTy::F32) => Ok(InlineAsmType::F32),
93            ty::Float(FloatTy::F64) => Ok(InlineAsmType::F64),
94            ty::Float(FloatTy::F128) => Ok(InlineAsmType::F128),
95            ty::FnPtr(..) => Ok(asm_ty_isize),
96            ty::RawPtr(elem_ty, _) => {
97                if self.is_thin_ptr_ty(elem_ty) {
98                    Ok(asm_ty_isize)
99                } else {
100                    Err(NonAsmTypeReason::NotSizedPtr(ty))
101                }
102            }
103            ty::Adt(adt, args) if adt.repr().simd() => {
104                let fields = &adt.non_enum_variant().fields;
105                let field = &fields[FieldIdx::ZERO];
106                let elem_ty = field.ty(self.tcx(), args);
107
108                let (size, ty) = match elem_ty.kind() {
109                    ty::Array(ty, len) => {
110                        let len = self.tcx().normalize_erasing_regions(self.typing_env, *len);
111                        if let Some(len) = len.try_to_target_usize(self.tcx()) {
112                            (len, *ty)
113                        } else {
114                            return Err(NonAsmTypeReason::UnevaluatedSIMDArrayLength(
115                                field.did, len,
116                            ));
117                        }
118                    }
119                    _ => (fields.len() as u64, elem_ty),
120                };
121
122                match ty.kind() {
123                    ty::Int(IntTy::I8) | ty::Uint(UintTy::U8) => Ok(InlineAsmType::VecI8(size)),
124                    ty::Int(IntTy::I16) | ty::Uint(UintTy::U16) => Ok(InlineAsmType::VecI16(size)),
125                    ty::Int(IntTy::I32) | ty::Uint(UintTy::U32) => Ok(InlineAsmType::VecI32(size)),
126                    ty::Int(IntTy::I64) | ty::Uint(UintTy::U64) => Ok(InlineAsmType::VecI64(size)),
127                    ty::Int(IntTy::I128) | ty::Uint(UintTy::U128) => {
128                        Ok(InlineAsmType::VecI128(size))
129                    }
130                    ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize) => {
131                        Ok(match self.tcx().sess.target.pointer_width {
132                            16 => InlineAsmType::VecI16(size),
133                            32 => InlineAsmType::VecI32(size),
134                            64 => InlineAsmType::VecI64(size),
135                            width => bug!("unsupported pointer width: {width}"),
136                        })
137                    }
138                    ty::Float(FloatTy::F16) => Ok(InlineAsmType::VecF16(size)),
139                    ty::Float(FloatTy::F32) => Ok(InlineAsmType::VecF32(size)),
140                    ty::Float(FloatTy::F64) => Ok(InlineAsmType::VecF64(size)),
141                    ty::Float(FloatTy::F128) => Ok(InlineAsmType::VecF128(size)),
142                    _ => Err(NonAsmTypeReason::InvalidElement(field.did, ty)),
143                }
144            }
145            ty::Infer(_) => bug!("unexpected infer ty in asm operand"),
146            _ => Err(NonAsmTypeReason::Invalid(ty)),
147        }
148    }
149
150    fn check_asm_operand_type(
151        &self,
152        idx: usize,
153        reg: InlineAsmRegOrRegClass,
154        expr: &'tcx hir::Expr<'tcx>,
155        template: &[InlineAsmTemplatePiece],
156        is_input: bool,
157        tied_input: Option<(&'tcx hir::Expr<'tcx>, Option<InlineAsmType>)>,
158    ) -> Option<InlineAsmType> {
159        let ty = self.expr_ty(expr);
160        if ty.has_non_region_infer() {
161            bug!("inference variable in asm operand ty: {:?} {:?}", expr, ty);
162        }
163
164        let asm_ty = match *ty.kind() {
165            // `!` is allowed for input but not for output (issue #87802)
166            ty::Never if is_input => return None,
167            _ if ty.references_error() => return None,
168            ty::Adt(adt, args) if self.tcx().is_lang_item(adt.did(), LangItem::MaybeUninit) => {
169                let fields = &adt.non_enum_variant().fields;
170                let ty = fields[FieldIdx::from_u32(1)].ty(self.tcx(), args);
171                // FIXME: Are we just trying to map to the `T` in `MaybeUninit<T>`?
172                // If so, just get it from the args.
173                let ty::Adt(ty, args) = ty.kind() else {
174                    unreachable!("expected first field of `MaybeUninit` to be an ADT")
175                };
176                assert!(
177                    ty.is_manually_drop(),
178                    "expected first field of `MaybeUninit` to be `ManuallyDrop`"
179                );
180                let fields = &ty.non_enum_variant().fields;
181                let ty = fields[FieldIdx::ZERO].ty(self.tcx(), args);
182                self.get_asm_ty(ty)
183            }
184            _ => self.get_asm_ty(ty),
185        };
186        let asm_ty = match asm_ty {
187            Ok(asm_ty) => asm_ty,
188            Err(reason) => {
189                match reason {
190                    NonAsmTypeReason::UnevaluatedSIMDArrayLength(did, len) => {
191                        let msg = format!("cannot evaluate SIMD vector length `{len}`");
192                        self.infcx
193                            .dcx()
194                            .struct_span_err(self.tcx().def_span(did), msg)
195                            .with_span_note(
196                                expr.span,
197                                "SIMD vector length needs to be known statically for use in `asm!`",
198                            )
199                            .emit();
200                    }
201                    NonAsmTypeReason::Invalid(ty) => {
202                        let msg = format!("cannot use value of type `{ty}` for inline assembly");
203                        self.infcx.dcx().struct_span_err(expr.span, msg).with_note(
204                            "only integers, floats, SIMD vectors, pointers and function pointers \
205                            can be used as arguments for inline assembly",
206                        ).emit();
207                    }
208                    NonAsmTypeReason::NotSizedPtr(ty) => {
209                        let msg = format!(
210                            "cannot use value of unsized pointer type `{ty}` for inline assembly"
211                        );
212                        self.infcx
213                            .dcx()
214                            .struct_span_err(expr.span, msg)
215                            .with_note("only sized pointers can be used in inline assembly")
216                            .emit();
217                    }
218                    NonAsmTypeReason::InvalidElement(did, ty) => {
219                        let msg = format!(
220                            "cannot use SIMD vector with element type `{ty}` for inline assembly"
221                        );
222                        self.infcx.dcx()
223                        .struct_span_err(self.tcx().def_span(did), msg).with_span_note(
224                            expr.span,
225                            "only integers, floats, SIMD vectors, pointers and function pointers \
226                            can be used as arguments for inline assembly",
227                        ).emit();
228                    }
229                }
230                return None;
231            }
232        };
233
234        // Check that the type implements Copy. The only case where this can
235        // possibly fail is for SIMD types which don't #[derive(Copy)].
236        if !self.tcx().type_is_copy_modulo_regions(self.typing_env, ty) {
237            let msg = "arguments for inline assembly must be copyable";
238            self.infcx
239                .dcx()
240                .struct_span_err(expr.span, msg)
241                .with_note(format!("`{ty}` does not implement the Copy trait"))
242                .emit();
243        }
244
245        // Ideally we wouldn't need to do this, but LLVM's register allocator
246        // really doesn't like it when tied operands have different types.
247        //
248        // This is purely an LLVM limitation, but we have to live with it since
249        // there is no way to hide this with implicit conversions.
250        //
251        // For the purposes of this check we only look at the `InlineAsmType`,
252        // which means that pointers and integers are treated as identical (modulo
253        // size).
254        if let Some((in_expr, Some(in_asm_ty))) = tied_input {
255            if in_asm_ty != asm_ty {
256                let msg = "incompatible types for asm inout argument";
257                let in_expr_ty = self.expr_ty(in_expr);
258                self.infcx
259                    .dcx()
260                    .struct_span_err(vec![in_expr.span, expr.span], msg)
261                    .with_span_label(in_expr.span, format!("type `{in_expr_ty}`"))
262                    .with_span_label(expr.span, format!("type `{ty}`"))
263                    .with_note(
264                        "asm inout arguments must have the same type, \
265                        unless they are both pointers or integers of the same size",
266                    )
267                    .emit();
268            }
269
270            // All of the later checks have already been done on the input, so
271            // let's not emit errors and warnings twice.
272            return Some(asm_ty);
273        }
274
275        // Check the type against the list of types supported by the selected
276        // register class.
277        let asm_arch = self.tcx().sess.asm_arch.unwrap();
278        let allow_experimental_reg = self.tcx().features().asm_experimental_reg();
279        let reg_class = reg.reg_class();
280        let supported_tys = reg_class.supported_types(asm_arch, allow_experimental_reg);
281        let Some((_, feature)) = supported_tys.iter().find(|&&(t, _)| t == asm_ty) else {
282            let mut err = if !allow_experimental_reg
283                && reg_class.supported_types(asm_arch, true).iter().any(|&(t, _)| t == asm_ty)
284            {
285                self.tcx().sess.create_feature_err(
286                    RegisterTypeUnstable { span: expr.span, ty },
287                    sym::asm_experimental_reg,
288                )
289            } else {
290                let msg = format!("type `{ty}` cannot be used with this register class");
291                let mut err = self.infcx.dcx().struct_span_err(expr.span, msg);
292                let supported_tys: Vec<_> =
293                    supported_tys.iter().map(|(t, _)| t.to_string()).collect();
294                err.note(format!(
295                    "register class `{}` supports these types: {}",
296                    reg_class.name(),
297                    supported_tys.join(", "),
298                ));
299                err
300            };
301            if let Some(suggest) = reg_class.suggest_class(asm_arch, asm_ty) {
302                err.help(format!("consider using the `{}` register class instead", suggest.name()));
303            }
304            err.emit();
305            return Some(asm_ty);
306        };
307
308        // Check whether the selected type requires a target feature. Note that
309        // this is different from the feature check we did earlier. While the
310        // previous check checked that this register class is usable at all
311        // with the currently enabled features, some types may only be usable
312        // with a register class when a certain feature is enabled. We check
313        // this here since it depends on the results of typeck.
314        //
315        // Also note that this check isn't run when the operand type is never
316        // (!). In that case we still need the earlier check to verify that the
317        // register class is usable at all.
318        if let Some(feature) = feature {
319            if !self.target_features.contains(feature) {
320                let msg = format!("`{feature}` target feature is not enabled");
321                self.infcx
322                    .dcx()
323                    .struct_span_err(expr.span, msg)
324                    .with_note(format!(
325                        "this is required to use type `{}` with register class `{}`",
326                        ty,
327                        reg_class.name(),
328                    ))
329                    .emit();
330                return Some(asm_ty);
331            }
332        }
333
334        // Check whether a modifier is suggested for using this type.
335        if let Some(ModifierInfo {
336            modifier: suggested_modifier,
337            result: suggested_result,
338            size: suggested_size,
339        }) = reg_class.suggest_modifier(asm_arch, asm_ty)
340        {
341            // Search for any use of this operand without a modifier and emit
342            // the suggestion for them.
343            let mut spans = vec![];
344            for piece in template {
345                if let &InlineAsmTemplatePiece::Placeholder { operand_idx, modifier, span } = piece
346                {
347                    if operand_idx == idx && modifier.is_none() {
348                        spans.push(span);
349                    }
350                }
351            }
352            if !spans.is_empty() {
353                let ModifierInfo {
354                    modifier: default_modifier,
355                    result: default_result,
356                    size: default_size,
357                } = reg_class.default_modifier(asm_arch).unwrap();
358                self.tcx().node_span_lint(
359                    lint::builtin::ASM_SUB_REGISTER,
360                    expr.hir_id,
361                    spans,
362                    |lint| {
363                        lint.primary_message("formatting may not be suitable for sub-register argument");
364                        lint.span_label(expr.span, "for this argument");
365                        lint.help(format!(
366                            "use `{{{idx}:{suggested_modifier}}}` to have the register formatted as `{suggested_result}` (for {suggested_size}-bit values)",
367                        ));
368                        lint.help(format!(
369                            "or use `{{{idx}:{default_modifier}}}` to keep the default formatting of `{default_result}` (for {default_size}-bit values)",
370                        ));
371                    },
372                );
373            }
374        }
375
376        Some(asm_ty)
377    }
378
379    pub fn check_asm(&self, asm: &hir::InlineAsm<'tcx>) {
380        let Some(asm_arch) = self.tcx().sess.asm_arch else {
381            self.infcx.dcx().delayed_bug("target architecture does not support asm");
382            return;
383        };
384        let allow_experimental_reg = self.tcx().features().asm_experimental_reg();
385        for (idx, &(op, op_sp)) in asm.operands.iter().enumerate() {
386            // Validate register classes against currently enabled target
387            // features. We check that at least one type is available for
388            // the enabled features.
389            //
390            // We ignore target feature requirements for clobbers: if the
391            // feature is disabled then the compiler doesn't care what we
392            // do with the registers.
393            //
394            // Note that this is only possible for explicit register
395            // operands, which cannot be used in the asm string.
396            if let Some(reg) = op.reg() {
397                // Some explicit registers cannot be used depending on the
398                // target. Reject those here.
399                if let InlineAsmRegOrRegClass::Reg(reg) = reg {
400                    if let InlineAsmReg::Err = reg {
401                        // `validate` will panic on `Err`, as an error must
402                        // already have been reported.
403                        continue;
404                    }
405                    if let Err(msg) = reg.validate(
406                        asm_arch,
407                        self.tcx().sess.relocation_model(),
408                        self.target_features,
409                        &self.tcx().sess.target,
410                        op.is_clobber(),
411                    ) {
412                        let msg = format!("cannot use register `{}`: {}", reg.name(), msg);
413                        self.infcx.dcx().span_err(op_sp, msg);
414                        continue;
415                    }
416                }
417
418                if !op.is_clobber() {
419                    let mut missing_required_features = vec![];
420                    let reg_class = reg.reg_class();
421                    if let InlineAsmRegClass::Err = reg_class {
422                        continue;
423                    }
424                    for &(_, feature) in reg_class.supported_types(asm_arch, allow_experimental_reg)
425                    {
426                        match feature {
427                            Some(feature) => {
428                                if self.target_features.contains(&feature) {
429                                    missing_required_features.clear();
430                                    break;
431                                } else {
432                                    missing_required_features.push(feature);
433                                }
434                            }
435                            None => {
436                                missing_required_features.clear();
437                                break;
438                            }
439                        }
440                    }
441
442                    // We are sorting primitive strs here and can use unstable sort here
443                    missing_required_features.sort_unstable();
444                    missing_required_features.dedup();
445                    match &missing_required_features[..] {
446                        [] => {}
447                        [feature] => {
448                            let msg = format!(
449                                "register class `{}` requires the `{}` target feature",
450                                reg_class.name(),
451                                feature
452                            );
453                            self.infcx.dcx().span_err(op_sp, msg);
454                            // register isn't enabled, don't do more checks
455                            continue;
456                        }
457                        features => {
458                            let msg = format!(
459                                "register class `{}` requires at least one of the following target features: {}",
460                                reg_class.name(),
461                                features
462                                    .iter()
463                                    .map(|f| f.as_str())
464                                    .intersperse(", ")
465                                    .collect::<String>(),
466                            );
467                            self.infcx.dcx().span_err(op_sp, msg);
468                            // register isn't enabled, don't do more checks
469                            continue;
470                        }
471                    }
472                }
473            }
474
475            match op {
476                hir::InlineAsmOperand::In { reg, expr } => {
477                    self.check_asm_operand_type(idx, reg, expr, asm.template, true, None);
478                }
479                hir::InlineAsmOperand::Out { reg, late: _, expr } => {
480                    if let Some(expr) = expr {
481                        self.check_asm_operand_type(idx, reg, expr, asm.template, false, None);
482                    }
483                }
484                hir::InlineAsmOperand::InOut { reg, late: _, expr } => {
485                    self.check_asm_operand_type(idx, reg, expr, asm.template, false, None);
486                }
487                hir::InlineAsmOperand::SplitInOut { reg, late: _, in_expr, out_expr } => {
488                    let in_ty =
489                        self.check_asm_operand_type(idx, reg, in_expr, asm.template, true, None);
490                    if let Some(out_expr) = out_expr {
491                        self.check_asm_operand_type(
492                            idx,
493                            reg,
494                            out_expr,
495                            asm.template,
496                            false,
497                            Some((in_expr, in_ty)),
498                        );
499                    }
500                }
501                hir::InlineAsmOperand::Const { anon_const } => {
502                    let ty = self.expr_ty(self.tcx().hir_body(anon_const.body).value);
503                    match ty.kind() {
504                        ty::Error(_) => {}
505                        _ if ty.is_integral() => {}
506                        _ => {
507                            self.infcx
508                                .dcx()
509                                .struct_span_err(op_sp, "invalid type for `const` operand")
510                                .with_span_label(
511                                    self.tcx().def_span(anon_const.def_id),
512                                    format!("is {} `{}`", ty.kind().article(), ty),
513                                )
514                                .with_help("`const` operands must be of an integer type")
515                                .emit();
516                        }
517                    }
518                }
519                // Typeck has checked that SymFn refers to a function.
520                hir::InlineAsmOperand::SymFn { expr } => {
521                    let ty = self.expr_ty(expr);
522                    match ty.kind() {
523                        ty::FnDef(..) => {}
524                        ty::Error(_) => {}
525                        _ => {
526                            self.infcx
527                                .dcx()
528                                .struct_span_err(op_sp, "invalid `sym` operand")
529                                .with_span_label(
530                                    expr.span,
531                                    format!("is {} `{}`", ty.kind().article(), ty),
532                                )
533                                .with_help(
534                                    "`sym` operands must refer to either a function or a static",
535                                )
536                                .emit();
537                        }
538                    }
539                }
540                // AST lowering guarantees that SymStatic points to a static.
541                hir::InlineAsmOperand::SymStatic { .. } => {}
542                // No special checking is needed for labels.
543                hir::InlineAsmOperand::Label { .. } => {}
544            }
545        }
546    }
547}