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}