1use rustc_abi::FieldIdx;
2use rustc_ast::InlineAsmTemplatePiece;
3use rustc_data_structures::fx::FxIndexSet;
4use rustc_errors::{Diag, DiagCtxtHandle, Diagnostic, Level};
5use rustc_hir::def_id::DefId;
6use rustc_hir::{self as hir, LangItem};
7use rustc_middle::bug;
8use rustc_middle::ty::{
9 self, Article, FloatTy, IntTy, Ty, TyCtxt, TypeVisitableExt, UintTy, Unnormalized,
10};
11use rustc_session::lint;
12use rustc_span::def_id::LocalDefId;
13use rustc_span::{ErrorGuaranteed, Span, Symbol, sym};
14use rustc_target::asm::{
15 InlineAsmReg, InlineAsmRegClass, InlineAsmRegOrRegClass, InlineAsmType, ModifierInfo,
16};
17use rustc_trait_selection::infer::InferCtxtExt;
18
19use crate::FnCtxt;
20use crate::errors::RegisterTypeUnstable;
21
22pub(crate) struct InlineAsmCtxt<'a, 'tcx> {
23 target_features: &'tcx FxIndexSet<Symbol>,
24 fcx: &'a FnCtxt<'a, '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 EmptySIMDArray(Ty<'tcx>),
33 Tainted(ErrorGuaranteed),
34}
35
36impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> {
37 pub(crate) fn new(fcx: &'a FnCtxt<'a, 'tcx>, def_id: LocalDefId) -> Self {
38 InlineAsmCtxt { target_features: fcx.tcx.asm_target_features(def_id), fcx }
39 }
40
41 fn tcx(&self) -> TyCtxt<'tcx> {
42 self.fcx.tcx
43 }
44
45 fn expr_ty(&self, expr: &hir::Expr<'tcx>) -> Ty<'tcx> {
46 let ty = self.fcx.typeck_results.borrow().expr_ty_adjusted(expr);
47 let ty = self.fcx.try_structurally_resolve_type(expr.span, ty);
48 if ty.has_non_region_infer() {
49 Ty::new_misc_error(self.tcx())
50 } else {
51 self.tcx().erase_and_anonymize_regions(ty)
52 }
53 }
54
55 fn is_thin_ptr_ty(&self, span: Span, ty: Ty<'tcx>) -> bool {
57 if self.fcx.type_is_sized_modulo_regions(self.fcx.param_env, ty) {
60 return true;
61 }
62 if let ty::Foreign(..) = self.fcx.try_structurally_resolve_type(span, ty).kind() {
63 return true;
64 }
65 false
66 }
67
68 fn get_asm_ty(
69 &self,
70 span: Span,
71 ty: Ty<'tcx>,
72 ) -> Result<InlineAsmType, NonAsmTypeReason<'tcx>> {
73 let asm_ty_isize = match self.tcx().sess.target.pointer_width {
74 16 => InlineAsmType::I16,
75 32 => InlineAsmType::I32,
76 64 => InlineAsmType::I64,
77 width => ::rustc_middle::util::bug::bug_fmt(format_args!("unsupported pointer width: {0}",
width))bug!("unsupported pointer width: {width}"),
78 };
79
80 match *ty.kind() {
81 ty::Int(IntTy::I8) | ty::Uint(UintTy::U8) => Ok(InlineAsmType::I8),
82 ty::Int(IntTy::I16) | ty::Uint(UintTy::U16) => Ok(InlineAsmType::I16),
83 ty::Int(IntTy::I32) | ty::Uint(UintTy::U32) => Ok(InlineAsmType::I32),
84 ty::Int(IntTy::I64) | ty::Uint(UintTy::U64) => Ok(InlineAsmType::I64),
85 ty::Int(IntTy::I128) | ty::Uint(UintTy::U128) => Ok(InlineAsmType::I128),
86 ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize) => Ok(asm_ty_isize),
87 ty::Float(FloatTy::F16) => Ok(InlineAsmType::F16),
88 ty::Float(FloatTy::F32) => Ok(InlineAsmType::F32),
89 ty::Float(FloatTy::F64) => Ok(InlineAsmType::F64),
90 ty::Float(FloatTy::F128) => Ok(InlineAsmType::F128),
91 ty::FnPtr(..) => Ok(asm_ty_isize),
92 ty::RawPtr(elem_ty, _) => {
93 if self.is_thin_ptr_ty(span, elem_ty) {
94 Ok(asm_ty_isize)
95 } else {
96 Err(NonAsmTypeReason::NotSizedPtr(ty))
97 }
98 }
99 ty::Adt(adt, args) if adt.repr().simd() => {
100 if !adt.is_struct() {
101 let guar = self.fcx.dcx().span_delayed_bug(
102 span,
103 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("repr(simd) should only be used on structs, got {0}",
adt.descr()))
})format!("repr(simd) should only be used on structs, got {}", adt.descr()),
104 );
105 return Err(NonAsmTypeReason::Tainted(guar));
106 }
107
108 let fields = &adt.non_enum_variant().fields;
109 if fields.is_empty() {
110 return Err(NonAsmTypeReason::EmptySIMDArray(ty));
111 }
112 let field = &fields[FieldIdx::ZERO];
113 let elem_ty = field.ty(self.tcx(), args);
114
115 let (size, ty) = match *elem_ty.kind() {
116 ty::Array(ty, len) => {
117 let len = if self.fcx.next_trait_solver() {
120 self.fcx.try_structurally_resolve_const(span, len)
121 } else {
122 self.fcx.tcx.normalize_erasing_regions(
123 self.fcx.typing_env(self.fcx.param_env),
124 Unnormalized::new_wip(len),
125 )
126 };
127 let Some(len) = len.try_to_target_usize(self.tcx()) else {
128 return Err(NonAsmTypeReason::UnevaluatedSIMDArrayLength(
129 field.did, len,
130 ));
131 };
132 (len, ty)
133 }
134 _ => (fields.len() as u64, elem_ty),
135 };
136
137 match ty.kind() {
138 ty::Int(IntTy::I8) | ty::Uint(UintTy::U8) => Ok(InlineAsmType::VecI8(size)),
139 ty::Int(IntTy::I16) | ty::Uint(UintTy::U16) => Ok(InlineAsmType::VecI16(size)),
140 ty::Int(IntTy::I32) | ty::Uint(UintTy::U32) => Ok(InlineAsmType::VecI32(size)),
141 ty::Int(IntTy::I64) | ty::Uint(UintTy::U64) => Ok(InlineAsmType::VecI64(size)),
142 ty::Int(IntTy::I128) | ty::Uint(UintTy::U128) => {
143 Ok(InlineAsmType::VecI128(size))
144 }
145 ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize) => {
146 Ok(match self.tcx().sess.target.pointer_width {
147 16 => InlineAsmType::VecI16(size),
148 32 => InlineAsmType::VecI32(size),
149 64 => InlineAsmType::VecI64(size),
150 width => ::rustc_middle::util::bug::bug_fmt(format_args!("unsupported pointer width: {0}",
width))bug!("unsupported pointer width: {width}"),
151 })
152 }
153 ty::Float(FloatTy::F16) => Ok(InlineAsmType::VecF16(size)),
154 ty::Float(FloatTy::F32) => Ok(InlineAsmType::VecF32(size)),
155 ty::Float(FloatTy::F64) => Ok(InlineAsmType::VecF64(size)),
156 ty::Float(FloatTy::F128) => Ok(InlineAsmType::VecF128(size)),
157 _ => Err(NonAsmTypeReason::InvalidElement(field.did, ty)),
158 }
159 }
160 ty::Infer(_) => ::rustc_middle::util::bug::bug_fmt(format_args!("unexpected infer ty in asm operand"))bug!("unexpected infer ty in asm operand"),
161 _ => Err(NonAsmTypeReason::Invalid(ty)),
162 }
163 }
164
165 fn check_asm_operand_type(
166 &self,
167 idx: usize,
168 reg: InlineAsmRegOrRegClass,
169 expr: &'tcx hir::Expr<'tcx>,
170 template: &[InlineAsmTemplatePiece],
171 is_input: bool,
172 tied_input: Option<(&'tcx hir::Expr<'tcx>, Option<InlineAsmType>)>,
173 ) -> Option<InlineAsmType> {
174 struct FormattingSubRegisterArg<'a> {
175 expr_span: Span,
176 idx: usize,
177 suggested_modifier: char,
178 suggested_result: &'a str,
179 suggested_size: u16,
180 default_modifier: char,
181 default_result: &'a str,
182 default_size: u16,
183 }
184
185 impl<'a, 'b> Diagnostic<'a, ()> for FormattingSubRegisterArg<'b> {
186 fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, ()> {
187 let Self {
188 expr_span,
189 idx,
190 suggested_modifier,
191 suggested_result,
192 suggested_size,
193 default_modifier,
194 default_result,
195 default_size,
196 } = self;
197 Diag::new(dcx, level, "formatting may not be suitable for sub-register argument")
198 .with_span_label(expr_span, "for this argument")
199 .with_help(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("use `{{{0}:{1}}}` to have the register formatted as `{2}` (for {3}-bit values)",
idx, suggested_modifier, suggested_result, suggested_size))
})format!(
200 "use `{{{idx}:{suggested_modifier}}}` to have the register formatted as `{suggested_result}` (for {suggested_size}-bit values)",
201 ))
202 .with_help(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("or use `{{{0}:{1}}}` to keep the default formatting of `{2}` (for {3}-bit values)",
idx, default_modifier, default_result, default_size))
})format!(
203 "or use `{{{idx}:{default_modifier}}}` to keep the default formatting of `{default_result}` (for {default_size}-bit values)",
204 ))
205 }
206 }
207
208 let ty = self.expr_ty(expr);
209 if ty.has_non_region_infer() {
210 ::rustc_middle::util::bug::bug_fmt(format_args!("inference variable in asm operand ty: {0:?} {1:?}",
expr, ty));bug!("inference variable in asm operand ty: {:?} {:?}", expr, ty);
211 }
212
213 let asm_ty = match *ty.kind() {
214 ty::Never if is_input => return None,
216 _ if ty.references_error() => return None,
217 ty::Adt(adt, args) if self.tcx().is_lang_item(adt.did(), LangItem::MaybeUninit) => {
218 let ty = args.type_at(0);
219 self.get_asm_ty(expr.span, ty)
220 }
221 _ => self.get_asm_ty(expr.span, ty),
222 };
223 let asm_ty = match asm_ty {
224 Ok(asm_ty) => asm_ty,
225 Err(reason) => {
226 match reason {
227 NonAsmTypeReason::UnevaluatedSIMDArrayLength(did, len) => {
228 let msg = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("cannot evaluate SIMD vector length `{0}`",
len))
})format!("cannot evaluate SIMD vector length `{len}`");
229 self.fcx
230 .dcx()
231 .struct_span_err(self.tcx().def_span(did), msg)
232 .with_span_note(
233 expr.span,
234 "SIMD vector length needs to be known statically for use in `asm!`",
235 )
236 .emit();
237 }
238 NonAsmTypeReason::Invalid(ty) => {
239 let msg = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("cannot use value of type `{0}` for inline assembly",
ty))
})format!("cannot use value of type `{ty}` for inline assembly");
240 self.fcx.dcx().struct_span_err(expr.span, msg).with_note(
241 "only integers, floats, SIMD vectors, pointers and function pointers \
242 can be used as arguments for inline assembly",
243 ).emit();
244 }
245 NonAsmTypeReason::NotSizedPtr(ty) => {
246 let msg = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("cannot use value of unsized pointer type `{0}` for inline assembly",
ty))
})format!(
247 "cannot use value of unsized pointer type `{ty}` for inline assembly"
248 );
249 self.fcx
250 .dcx()
251 .struct_span_err(expr.span, msg)
252 .with_note("only sized pointers can be used in inline assembly")
253 .emit();
254 }
255 NonAsmTypeReason::InvalidElement(did, ty) => {
256 let msg = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("cannot use SIMD vector with element type `{0}` for inline assembly",
ty))
})format!(
257 "cannot use SIMD vector with element type `{ty}` for inline assembly"
258 );
259 self.fcx.dcx()
260 .struct_span_err(self.tcx().def_span(did), msg).with_span_note(
261 expr.span,
262 "only integers, floats, SIMD vectors, pointers and function pointers \
263 can be used as arguments for inline assembly",
264 ).emit();
265 }
266 NonAsmTypeReason::EmptySIMDArray(ty) => {
267 let msg = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("use of empty SIMD vector `{0}`",
ty))
})format!("use of empty SIMD vector `{ty}`");
268 self.fcx.dcx().struct_span_err(expr.span, msg).emit();
269 }
270 NonAsmTypeReason::Tainted(_error_guard) => {
271 }
273 }
274 return None;
275 }
276 };
277
278 if !self.fcx.type_is_copy_modulo_regions(self.fcx.param_env, ty) {
281 let msg = "arguments for inline assembly must be copyable";
282 self.fcx
283 .dcx()
284 .struct_span_err(expr.span, msg)
285 .with_note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("`{0}` does not implement the Copy trait",
ty))
})format!("`{ty}` does not implement the Copy trait"))
286 .emit();
287 }
288
289 if let Some((in_expr, Some(in_asm_ty))) = tied_input {
299 if in_asm_ty != asm_ty {
300 let msg = "incompatible types for asm inout argument";
301 let in_expr_ty = self.expr_ty(in_expr);
302 self.fcx
303 .dcx()
304 .struct_span_err(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[in_expr.span, expr.span]))vec![in_expr.span, expr.span], msg)
305 .with_span_label(in_expr.span, ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("type `{0}`", in_expr_ty))
})format!("type `{in_expr_ty}`"))
306 .with_span_label(expr.span, ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("type `{0}`", ty))
})format!("type `{ty}`"))
307 .with_note(
308 "asm inout arguments must have the same type, \
309 unless they are both pointers or integers of the same size",
310 )
311 .emit();
312 }
313
314 return Some(asm_ty);
317 }
318
319 let asm_arch = self.tcx().sess.asm_arch.unwrap();
322 let allow_experimental_reg = self.tcx().features().asm_experimental_reg();
323 let reg_class = reg.reg_class();
324 let supported_tys = reg_class.supported_types(asm_arch, allow_experimental_reg);
325 let Some((_, feature)) = supported_tys.iter().find(|&&(t, _)| t == asm_ty) else {
326 let mut err = if !allow_experimental_reg
327 && reg_class.supported_types(asm_arch, true).iter().any(|&(t, _)| t == asm_ty)
328 {
329 self.tcx().sess.create_feature_err(
330 RegisterTypeUnstable { span: expr.span, ty },
331 sym::asm_experimental_reg,
332 )
333 } else {
334 let msg = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("type `{0}` cannot be used with this register class",
ty))
})format!("type `{ty}` cannot be used with this register class");
335 let mut err = self.fcx.dcx().struct_span_err(expr.span, msg);
336 let supported_tys: Vec<_> =
337 supported_tys.iter().map(|(t, _)| t.to_string()).collect();
338 err.note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("register class `{0}` supports these types: {1}",
reg_class.name(), supported_tys.join(", ")))
})format!(
339 "register class `{}` supports these types: {}",
340 reg_class.name(),
341 supported_tys.join(", "),
342 ));
343 err
344 };
345 if let Some(suggest) = reg_class.suggest_class(asm_arch, asm_ty) {
346 err.help(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("consider using the `{0}` register class instead",
suggest.name()))
})format!("consider using the `{}` register class instead", suggest.name()));
347 }
348 err.emit();
349 return Some(asm_ty);
350 };
351
352 if let Some(feature) = feature {
363 if !self.target_features.contains(feature) {
364 let msg = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("`{0}` target feature is not enabled",
feature))
})format!("`{feature}` target feature is not enabled");
365 self.fcx
366 .dcx()
367 .struct_span_err(expr.span, msg)
368 .with_note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("this is required to use type `{0}` with register class `{1}`",
ty, reg_class.name()))
})format!(
369 "this is required to use type `{}` with register class `{}`",
370 ty,
371 reg_class.name(),
372 ))
373 .emit();
374 return Some(asm_ty);
375 }
376 }
377
378 if let Some(ModifierInfo {
380 modifier: suggested_modifier,
381 result: suggested_result,
382 size: suggested_size,
383 }) = reg_class.suggest_modifier(asm_arch, asm_ty)
384 {
385 let mut spans = ::alloc::vec::Vec::new()vec![];
388 for piece in template {
389 if let &InlineAsmTemplatePiece::Placeholder { operand_idx, modifier, span } = piece
390 {
391 if operand_idx == idx && modifier.is_none() {
392 spans.push(span);
393 }
394 }
395 }
396 if !spans.is_empty() {
397 let ModifierInfo {
398 modifier: default_modifier,
399 result: default_result,
400 size: default_size,
401 } = reg_class.default_modifier(asm_arch).unwrap();
402 self.tcx().emit_node_span_lint(
403 lint::builtin::ASM_SUB_REGISTER,
404 expr.hir_id,
405 spans,
406 FormattingSubRegisterArg {
407 expr_span: expr.span,
408 idx,
409 suggested_modifier,
410 suggested_result,
411 suggested_size,
412 default_modifier,
413 default_result,
414 default_size,
415 },
416 );
417 }
418 }
419
420 Some(asm_ty)
421 }
422
423 pub(crate) fn check_asm(&self, asm: &hir::InlineAsm<'tcx>) {
424 let Some(asm_arch) = self.tcx().sess.asm_arch else {
425 self.fcx.dcx().delayed_bug("target architecture does not support asm");
426 return;
427 };
428 let allow_experimental_reg = self.tcx().features().asm_experimental_reg();
429 for (idx, &(op, op_sp)) in asm.operands.iter().enumerate() {
430 if let Some(reg) = op.reg() {
441 if let InlineAsmRegOrRegClass::Reg(reg) = reg {
444 if let InlineAsmReg::Err = reg {
445 continue;
448 }
449 if let Err(msg) = reg.validate(
450 asm_arch,
451 self.tcx().sess.relocation_model(),
452 self.target_features,
453 &self.tcx().sess.target,
454 op.is_clobber(),
455 ) {
456 let msg = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("cannot use register `{0}`: {1}",
reg.name(), msg))
})format!("cannot use register `{}`: {}", reg.name(), msg);
457 self.fcx.dcx().span_err(op_sp, msg);
458 continue;
459 }
460 }
461
462 if !op.is_clobber() {
463 let mut missing_required_features = ::alloc::vec::Vec::new()vec![];
464 let reg_class = reg.reg_class();
465 if let InlineAsmRegClass::Err = reg_class {
466 continue;
467 }
468 for &(_, feature) in reg_class.supported_types(asm_arch, allow_experimental_reg)
469 {
470 match feature {
471 Some(feature) => {
472 if self.target_features.contains(&feature) {
473 missing_required_features.clear();
474 break;
475 } else {
476 missing_required_features.push(feature);
477 }
478 }
479 None => {
480 missing_required_features.clear();
481 break;
482 }
483 }
484 }
485
486 missing_required_features.sort_unstable();
488 missing_required_features.dedup();
489 match &missing_required_features[..] {
490 [] => {}
491 [feature] => {
492 let msg = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("register class `{0}` requires the `{1}` target feature",
reg_class.name(), feature))
})format!(
493 "register class `{}` requires the `{}` target feature",
494 reg_class.name(),
495 feature
496 );
497 self.fcx.dcx().span_err(op_sp, msg);
498 continue;
500 }
501 features => {
502 let msg = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("register class `{0}` requires at least one of the following target features: {1}",
reg_class.name(),
features.iter().map(|f|
f.as_str()).intersperse(", ").collect::<String>()))
})format!(
503 "register class `{}` requires at least one of the following target features: {}",
504 reg_class.name(),
505 features
506 .iter()
507 .map(|f| f.as_str())
508 .intersperse(", ")
509 .collect::<String>(),
510 );
511 self.fcx.dcx().span_err(op_sp, msg);
512 continue;
514 }
515 }
516 }
517 }
518
519 match op {
520 hir::InlineAsmOperand::In { reg, expr } => {
521 self.check_asm_operand_type(idx, reg, expr, asm.template, true, None);
522 }
523 hir::InlineAsmOperand::Out { reg, late: _, expr } => {
524 if let Some(expr) = expr {
525 self.check_asm_operand_type(idx, reg, expr, asm.template, false, None);
526 }
527 }
528 hir::InlineAsmOperand::InOut { reg, late: _, expr } => {
529 self.check_asm_operand_type(idx, reg, expr, asm.template, false, None);
530 }
531 hir::InlineAsmOperand::SplitInOut { reg, late: _, in_expr, out_expr } => {
532 let in_ty =
533 self.check_asm_operand_type(idx, reg, in_expr, asm.template, true, None);
534 if let Some(out_expr) = out_expr {
535 self.check_asm_operand_type(
536 idx,
537 reg,
538 out_expr,
539 asm.template,
540 false,
541 Some((in_expr, in_ty)),
542 );
543 }
544 }
545 hir::InlineAsmOperand::Const { anon_const } => {
546 let ty = self.expr_ty(self.tcx().hir_body(anon_const.body).value);
547 match ty.kind() {
548 ty::Error(_) => {}
549 _ if ty.is_integral() => {}
550 _ => {
551 self.fcx
552 .dcx()
553 .struct_span_err(op_sp, "invalid type for `const` operand")
554 .with_span_label(
555 self.tcx().def_span(anon_const.def_id),
556 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("is {0} `{1}`", ty.kind().article(),
ty))
})format!("is {} `{}`", ty.kind().article(), ty),
557 )
558 .with_help("`const` operands must be of an integer type")
559 .emit();
560 }
561 }
562 }
563 hir::InlineAsmOperand::SymFn { expr } => {
565 let ty = self.expr_ty(expr);
566 match ty.kind() {
567 ty::FnDef(..) => {}
568 ty::Error(_) => {}
569 _ => {
570 self.fcx
571 .dcx()
572 .struct_span_err(op_sp, "invalid `sym` operand")
573 .with_span_label(
574 expr.span,
575 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("is {0} `{1}`", ty.kind().article(),
ty))
})format!("is {} `{}`", ty.kind().article(), ty),
576 )
577 .with_help(
578 "`sym` operands must refer to either a function or a static",
579 )
580 .emit();
581 }
582 }
583 }
584 hir::InlineAsmOperand::SymStatic { .. } => {}
586 hir::InlineAsmOperand::Label { .. } => {}
588 }
589 }
590 }
591}