rustc_lint/gpukernel_abi.rs
1use std::iter;
2
3use rustc_abi::ExternAbi;
4use rustc_hir::attrs::AttributeKind;
5use rustc_hir::{self as hir, find_attr};
6use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable};
7use rustc_session::{declare_lint, declare_lint_pass};
8use rustc_span::Span;
9use rustc_span::def_id::LocalDefId;
10
11use crate::lints::{ImproperGpuKernelArg, MissingGpuKernelExportName};
12use crate::{LateContext, LateLintPass, LintContext};
13
14declare_lint! {
15 /// The `improper_gpu_kernel_arg` lint detects incorrect use of types in `gpu-kernel`
16 /// arguments.
17 ///
18 /// ### Example
19 ///
20 /// ```rust,ignore (fails on non-GPU targets)
21 /// #[unsafe(no_mangle)]
22 /// extern "gpu-kernel" fn kernel(_: [i32; 10]) {}
23 /// ```
24 ///
25 /// This will produce:
26 ///
27 /// ```text
28 /// warning: passing type `[i32; 10]` to a function with "gpu-kernel" ABI may have unexpected behavior
29 /// --> t.rs:2:34
30 /// |
31 /// 2 | extern "gpu-kernel" fn kernel(_: [i32; 10]) {}
32 /// | ^^^^^^^^^
33 /// |
34 /// = help: use primitive types and raw pointers to get reliable behavior
35 /// = note: `#[warn(improper_gpu_kernel_arg)]` on by default
36 /// ```
37 ///
38 /// ### Explanation
39 ///
40 /// The compiler has several checks to verify that types used as arguments in `gpu-kernel`
41 /// functions follow certain rules to ensure proper compatibility with the foreign interfaces.
42 /// This lint is issued when it detects a probable mistake in a signature.
43 IMPROPER_GPU_KERNEL_ARG,
44 Warn,
45 "GPU kernel entry points have a limited ABI"
46}
47
48declare_lint! {
49 /// The `missing_gpu_kernel_export_name` lint detects `gpu-kernel` functions that have a mangled name.
50 ///
51 /// ### Example
52 ///
53 /// ```rust,ignore (fails on non-GPU targets)
54 /// extern "gpu-kernel" fn kernel() { }
55 /// ```
56 ///
57 /// This will produce:
58 ///
59 /// ```text
60 /// warning: function with the "gpu-kernel" ABI has a mangled name
61 /// --> t.rs:1:1
62 /// |
63 /// 1 | extern "gpu-kernel" fn kernel() {}
64 /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
65 /// |
66 /// = help: use `unsafe(no_mangle)` or `unsafe(export_name = "<name>")`
67 /// = note: mangled names make it hard to find the kernel, this is usually not intended
68 /// = note: `#[warn(missing_gpu_kernel_export_name)]` on by default
69 /// ```
70 ///
71 /// ### Explanation
72 ///
73 /// `gpu-kernel` functions are usually searched by name in the compiled file.
74 /// A mangled name is usually unintentional as it would need to be searched by the mangled name.
75 ///
76 /// To use an unmangled name for the kernel, either `no_mangle` or `export_name` can be used.
77 /// ```rust,ignore (fails on non-GPU targets)
78 /// // Can be found by the name "kernel"
79 /// #[unsafe(no_mangle)]
80 /// extern "gpu-kernel" fn kernel() { }
81 ///
82 /// // Can be found by the name "new_name"
83 /// #[unsafe(export_name = "new_name")]
84 /// extern "gpu-kernel" fn other_kernel() { }
85 /// ```
86 MISSING_GPU_KERNEL_EXPORT_NAME,
87 Warn,
88 "mangled gpu-kernel function"
89}
90
91declare_lint_pass!(ImproperGpuKernelLint => [
92 IMPROPER_GPU_KERNEL_ARG,
93 MISSING_GPU_KERNEL_EXPORT_NAME,
94]);
95
96/// Check for valid and invalid types.
97struct CheckGpuKernelTypes<'tcx> {
98 tcx: TyCtxt<'tcx>,
99 // If one or more invalid types were encountered while folding.
100 has_invalid: bool,
101}
102
103impl<'tcx> TypeFolder<TyCtxt<'tcx>> for CheckGpuKernelTypes<'tcx> {
104 fn cx(&self) -> TyCtxt<'tcx> {
105 self.tcx
106 }
107
108 fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
109 match ty.kind() {
110 ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) => {}
111 // Thin pointers are allowed but fat pointers with metadata are not
112 ty::RawPtr(_, _) => {
113 if !ty.pointee_metadata_ty_or_projection(self.tcx).is_unit() {
114 self.has_invalid = true;
115 }
116 }
117
118 ty::Adt(_, _)
119 | ty::Alias(_, _)
120 | ty::Array(_, _)
121 | ty::Bound(_, _)
122 | ty::Closure(_, _)
123 | ty::Coroutine(_, _)
124 | ty::CoroutineClosure(_, _)
125 | ty::CoroutineWitness(..)
126 | ty::Dynamic(_, _)
127 | ty::FnDef(_, _)
128 | ty::FnPtr(..)
129 | ty::Foreign(_)
130 | ty::Never
131 | ty::Pat(_, _)
132 | ty::Placeholder(_)
133 | ty::Ref(_, _, _)
134 | ty::Slice(_)
135 | ty::Str
136 | ty::Tuple(_) => self.has_invalid = true,
137
138 _ => return ty.super_fold_with(self),
139 }
140 ty
141 }
142}
143
144/// `ImproperGpuKernelLint` checks `gpu-kernel` function definitions:
145///
146/// - `extern "gpu-kernel" fn` arguments should be primitive types.
147/// - `extern "gpu-kernel" fn` should have an unmangled name.
148impl<'tcx> LateLintPass<'tcx> for ImproperGpuKernelLint {
149 fn check_fn(
150 &mut self,
151 cx: &LateContext<'tcx>,
152 kind: hir::intravisit::FnKind<'tcx>,
153 decl: &'tcx hir::FnDecl<'_>,
154 _: &'tcx hir::Body<'_>,
155 span: Span,
156 id: LocalDefId,
157 ) {
158 use hir::intravisit::FnKind;
159
160 let abi = match kind {
161 FnKind::ItemFn(_, _, header, ..) => header.abi,
162 FnKind::Method(_, sig, ..) => sig.header.abi,
163 _ => return,
164 };
165
166 if abi != ExternAbi::GpuKernel {
167 return;
168 }
169
170 let sig = cx.tcx.fn_sig(id).instantiate_identity();
171 let sig = cx.tcx.instantiate_bound_regions_with_erased(sig);
172
173 for (input_ty, input_hir) in iter::zip(sig.inputs(), decl.inputs) {
174 let mut checker = CheckGpuKernelTypes { tcx: cx.tcx, has_invalid: false };
175 input_ty.fold_with(&mut checker);
176 if checker.has_invalid {
177 cx.tcx.emit_node_span_lint(
178 IMPROPER_GPU_KERNEL_ARG,
179 input_hir.hir_id,
180 input_hir.span,
181 ImproperGpuKernelArg { ty: *input_ty },
182 );
183 }
184 }
185
186 // Check for no_mangle/export_name, so the kernel can be found when querying the compiled object for the kernel function by name
187 if !find_attr!(
188 cx.tcx.get_all_attrs(id),
189 AttributeKind::NoMangle(..) | AttributeKind::ExportName { .. }
190 ) {
191 cx.emit_span_lint(MISSING_GPU_KERNEL_EXPORT_NAME, span, MissingGpuKernelExportName);
192 }
193 }
194}