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