rustc_hir_analysis/check/
intrinsicck.rs

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