Skip to main content

cubecl_wgpu/compiler/
base.rs

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