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