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 fn is_thin_ptr_ty(&self, ty: Ty<'tcx>) -> bool {
65 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 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 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 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 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 return Some(asm_ty);
273 }
274
275 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 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 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 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 if let Some(reg) = op.reg() {
397 if let InlineAsmRegOrRegClass::Reg(reg) = reg {
400 if let InlineAsmReg::Err = reg {
401 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 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 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 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 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 hir::InlineAsmOperand::SymStatic { .. } => {}
542 hir::InlineAsmOperand::Label { .. } => {}
544 }
545 }
546 }
547}