Skip to main content

cubecl_wgpu/compiler/
base.rs

1use std::fmt::Display;
2
3use cubecl_core::{
4    Compiler, WgpuCompilationOptions,
5    prelude::{CompiledKernel, KernelDefinition},
6    server::{ComputeServer, LaunchError, ResourceLimitError},
7};
8#[cfg(feature = "msl")]
9use cubecl_cpp::shared::MslComputeKernel;
10use cubecl_environment::backtrace::BackTrace;
11use cubecl_ir::DeviceProperties;
12use cubecl_runtime::compiler::CompilationError;
13use derive_more::derive::From;
14
15#[cfg(feature = "spirv")]
16use crate::ParamsTransfer;
17use crate::{CompilerInfo, WgpuServer};
18
19use super::wgsl;
20
21#[cfg(feature = "msl")]
22pub use cubecl_cpp::MslCompiler;
23#[cfg(feature = "spirv")]
24pub use cubecl_spirv::SpirvCompiler;
25pub use wgsl::WgslCompiler;
26
27/// Compiler that dispatches to the most appropriate shader language backend for the active
28/// `wgpu` backend.
29///
30/// The variant is selected at runtime by [`WgpuCompiler::init`] based on the `wgpu::Backend`
31/// in use and the enabled cargo features (`spirv`, `msl`).
32#[allow(clippy::large_enum_variant)]
33#[derive(Debug, Clone)]
34pub enum AutoCompiler {
35    /// WGSL backend, available on every `wgpu` backend.
36    Wgsl(WgslCompiler),
37    /// SPIR-V backend used on Vulkan when the device supports the required features.
38    #[cfg(feature = "spirv")]
39    SpirV(cubecl_spirv::SpirvCompiler),
40    /// Metal Shading Language backend used on the Metal backend.
41    #[cfg(feature = "msl")]
42    Msl(MslCompiler),
43}
44
45/// Owned compiled kernel representation matching the variants of [`AutoCompiler`].
46#[derive(From)]
47#[allow(clippy::large_enum_variant)]
48pub enum AutoRepresentation {
49    /// WGSL compute shader source.
50    Wgsl(wgsl::ComputeShader),
51    /// Compiled SPIR-V kernel.
52    #[cfg(feature = "spirv")]
53    SpirV(cubecl_spirv::SpirvKernel),
54    /// Compiled Metal Shading Language kernel.
55    #[cfg(feature = "msl")]
56    Msl(MslComputeKernel),
57}
58
59/// Borrowed counterpart of [`AutoRepresentation`], useful when only read access is needed.
60#[derive(From, Clone, Copy)]
61#[allow(clippy::large_enum_variant)]
62pub enum AutoRepresentationRef<'a> {
63    /// Borrowed WGSL compute shader.
64    Wgsl(&'a wgsl::ComputeShader),
65    /// Borrowed SPIR-V kernel.
66    #[cfg(feature = "spirv")]
67    SpirV(&'a cubecl_spirv::SpirvKernel),
68    /// Borrowed Metal Shading Language kernel.
69    #[cfg(feature = "msl")]
70    Msl(&'a MslComputeKernel),
71}
72
73#[cfg(feature = "spirv")]
74impl AutoRepresentation {
75    /// Returns the SPIR-V kernel if this representation is the SPIR-V variant.
76    pub fn as_spirv(&self) -> Option<&cubecl_spirv::SpirvKernel> {
77        match self {
78            AutoRepresentation::SpirV(repr) => Some(repr),
79            _ => None,
80        }
81    }
82}
83
84#[cfg(feature = "msl")]
85impl AutoRepresentation {
86    /// Returns the MSL kernel if this representation is the MSL variant.
87    pub fn as_msl(&self) -> Option<&MslComputeKernel> {
88        match self {
89            AutoRepresentation::Msl(repr) => Some(repr),
90            _ => None,
91        }
92    }
93}
94
95impl AutoRepresentation {
96    /// Borrow this representation as an [`AutoRepresentationRef`].
97    pub fn as_ref(&self) -> AutoRepresentationRef<'_> {
98        match self {
99            AutoRepresentation::Wgsl(compute_shader) => AutoRepresentationRef::Wgsl(compute_shader),
100            #[cfg(feature = "spirv")]
101            AutoRepresentation::SpirV(spirv_kernel) => AutoRepresentationRef::SpirV(spirv_kernel),
102            #[cfg(feature = "msl")]
103            AutoRepresentation::Msl(compute_shader) => AutoRepresentationRef::Msl(compute_shader),
104        }
105    }
106}
107
108impl Display for AutoRepresentation {
109    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
110        match self {
111            AutoRepresentation::Wgsl(compute_shader) => compute_shader.fmt(f),
112            #[cfg(feature = "spirv")]
113            AutoRepresentation::SpirV(spirv_kernel) => spirv_kernel.fmt(f),
114            #[cfg(feature = "msl")]
115            AutoRepresentation::Msl(compute_shader) => compute_shader.fmt(f),
116        }
117    }
118}
119
120impl Compiler for AutoCompiler {
121    type Representation = AutoRepresentation;
122
123    type CompilationOptions = WgpuCompilationOptions;
124
125    fn compile(
126        &mut self,
127        kernel: KernelDefinition,
128        compilation_options: &Self::CompilationOptions,
129    ) -> Result<Self::Representation, CompilationError> {
130        let kernel = match self {
131            AutoCompiler::Wgsl(wgsl_compiler) => {
132                Compiler::compile(wgsl_compiler, kernel, compilation_options)?.into()
133            }
134            #[cfg(feature = "spirv")]
135            AutoCompiler::SpirV(spirv_compiler) => {
136                Compiler::compile(spirv_compiler, kernel, compilation_options)?.into()
137            }
138            #[cfg(feature = "msl")]
139            AutoCompiler::Msl(msl_compiler) => {
140                // override compilation options with cpp compiler options for metal
141                use cubecl_cpp;
142                let compilation_options = cubecl_cpp::shared::CompilationOptions::default();
143                Compiler::compile(msl_compiler, kernel, &compilation_options)?.into()
144            }
145        };
146
147        Ok(kernel)
148    }
149
150    fn extension(&self) -> &'static str {
151        match self {
152            AutoCompiler::Wgsl(_) => "wgsl",
153            #[cfg(feature = "spirv")]
154            AutoCompiler::SpirV(_) => "spv",
155            #[cfg(feature = "msl")]
156            AutoCompiler::Msl(_) => "msl",
157        }
158    }
159}
160
161impl WgpuCompiler for AutoCompiler {
162    fn init(backend: wgpu::Backend, options: &WgpuCompilationOptions) -> Self {
163        let _ = options; // Unused without `spirv` feature
164        match backend {
165            #[cfg(feature = "spirv")]
166            wgpu::Backend::Vulkan if options.supports_vulkan_compiler => {
167                AutoCompiler::SpirV(Default::default())
168            }
169            #[cfg(feature = "msl")]
170            wgpu::Backend::Metal if options.supports_msl_compiler => {
171                AutoCompiler::Msl(Default::default())
172            }
173            _ => AutoCompiler::Wgsl(Default::default()),
174        }
175    }
176
177    fn compile_kernel(
178        &mut self,
179        server: &mut WgpuServer<AutoCompiler>,
180        kernel: <WgpuServer<AutoCompiler> as ComputeServer>::Kernel,
181        definition: KernelDefinition,
182    ) -> Result<CompiledKernel<Self>, CompilationError> {
183        match self {
184            AutoCompiler::Wgsl(_) => kernel.compile(definition, self, &server.compilation_options),
185            #[cfg(feature = "spirv")]
186            AutoCompiler::SpirV(_) => crate::vulkan::compile(self, server, kernel, definition),
187            #[cfg(feature = "msl")]
188            AutoCompiler::Msl(_) => kernel.compile(definition, self, &server.compilation_options),
189        }
190    }
191
192    fn lang_tag(&self) -> &'static str {
193        match self {
194            AutoCompiler::Wgsl(_) => "wgsl",
195            #[cfg(feature = "spirv")]
196            AutoCompiler::SpirV(_) => "spirv",
197            #[cfg(feature = "msl")]
198            AutoCompiler::Msl(_) => "msl",
199        }
200    }
201
202    fn validate_ir(
203        &self,
204        repr: &Option<Self::Representation>,
205        props: &DeviceProperties,
206    ) -> Result<(), LaunchError> {
207        let shared_bytes = repr.as_ref().map(|repr| match repr {
208            AutoRepresentation::Wgsl(repr) => repr.shared_memory_size,
209            #[cfg(feature = "msl")]
210            AutoRepresentation::Msl(repr) => repr.shared_memory_size,
211            #[cfg(feature = "spirv")]
212            AutoRepresentation::SpirV(repr) => repr.shared_size,
213        });
214        check_shared_memory(shared_bytes, props)
215    }
216
217    fn normalize_repr(
218        &self,
219        repr: Option<Self::Representation>,
220    ) -> (CompilerInfo, Option<AutoRepresentation>) {
221        let compiler_info = match &repr {
222            #[cfg(feature = "spirv")]
223            Some(AutoRepresentation::SpirV(repr)) => CompilerInfo::Vulkan {
224                params_transfer: match repr.immediate_size {
225                    Some(_) => ParamsTransfer::Immediate,
226                    None => ParamsTransfer::Uniform,
227                },
228            },
229            #[cfg(feature = "msl")]
230            Some(AutoRepresentation::Msl(_)) => CompilerInfo::Metal,
231            Some(AutoRepresentation::Wgsl(_)) => CompilerInfo::WGSL,
232            None => CompilerInfo::None,
233        };
234
235        (compiler_info, repr)
236    }
237}
238
239impl WgpuCompiler for WgslCompiler {
240    fn init(_backend: wgpu::Backend, _options: &WgpuCompilationOptions) -> Self {
241        Self
242    }
243
244    fn compile_kernel(
245        &mut self,
246        server: &mut WgpuServer<Self>,
247        kernel: <WgpuServer<Self> as ComputeServer>::Kernel,
248        definition: KernelDefinition,
249    ) -> Result<CompiledKernel<Self>, CompilationError> {
250        kernel.compile(definition, self, &server.compilation_options)
251    }
252
253    fn lang_tag(&self) -> &'static str {
254        "wgsl"
255    }
256
257    fn validate_ir(
258        &self,
259        repr: &Option<Self::Representation>,
260        props: &DeviceProperties,
261    ) -> Result<(), LaunchError> {
262        let shared_bytes = repr.as_ref().map(|repr| repr.shared_memory_size);
263        check_shared_memory(shared_bytes, props)
264    }
265
266    fn normalize_repr(
267        &self,
268        repr: Option<Self::Representation>,
269    ) -> (CompilerInfo, Option<AutoRepresentation>) {
270        (CompilerInfo::WGSL, repr.map(|r| r.into()))
271    }
272}
273
274#[cfg(feature = "msl")]
275impl WgpuCompiler for MslCompiler {
276    fn init(_backend: wgpu::Backend, _options: &WgpuCompilationOptions) -> Self {
277        Self::default()
278    }
279
280    fn compile_kernel(
281        &mut self,
282        _server: &mut WgpuServer<Self>,
283        kernel: <WgpuServer<Self> as ComputeServer>::Kernel,
284        definition: KernelDefinition,
285    ) -> Result<CompiledKernel<Self>, CompilationError> {
286        // The MSL compiler uses its own CompilationOptions, not WgpuCompilationOptions.
287        let compilation_options = cubecl_cpp::shared::CompilationOptions::default();
288        kernel.compile(definition, self, &compilation_options)
289    }
290
291    fn lang_tag(&self) -> &'static str {
292        "msl"
293    }
294
295    fn validate_ir(
296        &self,
297        repr: &Option<Self::Representation>,
298        props: &DeviceProperties,
299    ) -> Result<(), LaunchError> {
300        let shared_bytes = repr.as_ref().map(|repr| repr.shared_memory_size);
301        check_shared_memory(shared_bytes, props)
302    }
303
304    fn normalize_repr(
305        &self,
306        repr: Option<Self::Representation>,
307    ) -> (CompilerInfo, Option<AutoRepresentation>) {
308        (CompilerInfo::Metal, repr.map(|r| r.into()))
309    }
310}
311
312#[cfg(feature = "spirv")]
313impl WgpuCompiler for cubecl_spirv::SpirvCompiler {
314    fn init(_backend: wgpu::Backend, _options: &WgpuCompilationOptions) -> Self {
315        Self
316    }
317
318    fn compile_kernel(
319        &mut self,
320        server: &mut WgpuServer<Self>,
321        kernel: <WgpuServer<Self> as ComputeServer>::Kernel,
322        definition: KernelDefinition,
323    ) -> Result<CompiledKernel<Self>, CompilationError> {
324        crate::vulkan::compile(self, server, kernel, definition)
325    }
326
327    fn lang_tag(&self) -> &'static str {
328        "spirv"
329    }
330
331    fn validate_ir(
332        &self,
333        repr: &Option<Self::Representation>,
334        props: &DeviceProperties,
335    ) -> Result<(), LaunchError> {
336        let shared_bytes = repr.as_ref().map(|repr| repr.shared_size);
337        check_shared_memory(shared_bytes, props)
338    }
339
340    fn normalize_repr(
341        &self,
342        repr: Option<Self::Representation>,
343    ) -> (CompilerInfo, Option<AutoRepresentation>) {
344        let params_transfer = match repr.as_ref().and_then(|r| r.immediate_size) {
345            Some(_) => ParamsTransfer::Immediate,
346            None => ParamsTransfer::Uniform,
347        };
348        (
349            CompilerInfo::Vulkan { params_transfer },
350            repr.map(|r| r.into()),
351        )
352    }
353}
354
355fn check_shared_memory(
356    shared_bytes: Option<usize>,
357    props: &DeviceProperties,
358) -> Result<(), LaunchError> {
359    let max_smem = props.hardware.max_shared_memory_size;
360    if let Some(shared_bytes) = shared_bytes
361        && shared_bytes > max_smem
362    {
363        return Err(ResourceLimitError::SharedMemory {
364            requested: shared_bytes,
365            max: max_smem,
366            backtrace: BackTrace::capture(),
367        }
368        .into());
369    }
370    Ok(())
371}
372
373/// Extension trait implemented by every compiler usable with the `wgpu` runtime.
374///
375/// The base [`Compiler`] trait already exposes a `compile` method that turns a
376/// [`KernelDefinition`] into a backend representation. [`WgpuCompiler`] sits one level
377/// higher: it owns the wgpu-specific lifecycle around a [`CubeTask`](cubecl_runtime::compiler::CubeTask)
378/// kernel — initializing the compiler for a given `wgpu::Backend`, compiling a kernel using
379/// the server's [`WgpuCompilationOptions`], validating the resulting IR against the device,
380/// and projecting the typed representation into the runtime-erased [`AutoRepresentation`].
381pub trait WgpuCompiler: Compiler {
382    /// Build the compiler instance appropriate for the given `wgpu` backend.
383    ///
384    /// `options` is consulted to decide between alternative implementations (for example, to
385    /// opt into the SPIR-V compiler on Vulkan when the device advertises the required
386    /// features).
387    fn init(backend: wgpu::Backend, options: &WgpuCompilationOptions) -> Self;
388
389    /// Validate that the compiled representation fits within the device's resource limits.
390    ///
391    /// Today this checks shared memory usage; additional checks may be added without
392    /// breaking the contract.
393    fn validate_ir(
394        &self,
395        repr: &Option<Self::Representation>,
396        props: &DeviceProperties,
397    ) -> Result<(), LaunchError>;
398
399    /// Compile a runtime kernel into a [`CompiledKernel`] ready for pipeline creation.
400    ///
401    /// Distinct from [`Compiler::compile`], which only translates a [`KernelDefinition`].
402    /// This entry point operates on a full server-level kernel and pulls compilation
403    /// options from `server`, so its signature cannot collide with the base trait method.
404    fn compile_kernel(
405        &mut self,
406        server: &mut WgpuServer<Self>,
407        kernel: <WgpuServer<Self> as ComputeServer>::Kernel,
408        definition: KernelDefinition,
409    ) -> Result<CompiledKernel<Self>, CompilationError>;
410
411    /// Short identifier of the shader language produced by this compiler (e.g. `"wgsl"`).
412    ///
413    /// Used for logging and debug-info tagging.
414    fn lang_tag(&self) -> &'static str;
415
416    /// Normalize the backend-specific representation into the [`AutoRepresentation`] shared
417    /// by every wgpu compiler, and report the [`CompilerInfo`] derived from it.
418    ///
419    /// The [`CompilerInfo`] tells the server which parameter-passing strategy to use for
420    /// the resulting pipeline.
421    fn normalize_repr(
422        &self,
423        repr: Option<Self::Representation>,
424    ) -> (CompilerInfo, Option<AutoRepresentation>);
425}