Skip to main content

cubecl_wgpu/compiler/
base.rs

1use std::fmt::Display;
2
3use cubecl_core::{
4    Compiler, ExecutionMode, WgpuCompilationOptions,
5    ir::StorageType,
6    prelude::{CompiledKernel, KernelDefinition},
7    server::{ComputeServer, LaunchError, ResourceLimitError},
8};
9#[cfg(feature = "msl")]
10use cubecl_cpp::shared::MslComputeKernel;
11use cubecl_environment::backtrace::BackTrace;
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        definition: KernelDefinition,
196        mode: ExecutionMode,
197    ) -> Result<CompiledKernel<Self>, CompilationError> {
198        match self {
199            AutoCompiler::Wgsl(_) => kernel.compile(
200                definition,
201                self,
202                &server.compilation_options,
203                mode,
204                kernel.address_type(),
205            ),
206            #[cfg(feature = "spirv")]
207            AutoCompiler::SpirV(_) => {
208                #[cfg(feature = "spirv-dump")]
209                let (name, id) = (kernel.name().to_string(), kernel.id());
210                let compiled = crate::vulkan::compile(self, server, kernel, definition, mode)?;
211                #[cfg(feature = "spirv-dump")]
212                if let Some(spirv) = compiled.repr.as_ref().and_then(|r| r.as_spirv()) {
213                    crate::vulkan::dump_spirv(spirv, &name, id);
214                }
215                Ok(compiled)
216            }
217            #[cfg(feature = "msl")]
218            AutoCompiler::Msl(_) => kernel.compile(
219                definition,
220                self,
221                &server.compilation_options,
222                mode,
223                kernel.address_type(),
224            ),
225        }
226    }
227
228    fn lang_tag(&self) -> &'static str {
229        match self {
230            AutoCompiler::Wgsl(_) => "wgsl",
231            #[cfg(feature = "spirv")]
232            AutoCompiler::SpirV(_) => "spirv",
233            #[cfg(feature = "msl")]
234            AutoCompiler::Msl(_) => "msl",
235        }
236    }
237
238    fn validate_ir(
239        &self,
240        repr: &Option<Self::Representation>,
241        props: &DeviceProperties,
242    ) -> Result<(), LaunchError> {
243        let shared_bytes = repr.as_ref().map(|repr| match repr {
244            AutoRepresentation::Wgsl(repr) => repr.shared_memory_bytes(),
245            #[cfg(feature = "msl")]
246            AutoRepresentation::Msl(repr) => repr.shared_memory_size(),
247            #[cfg(feature = "spirv")]
248            AutoRepresentation::SpirV(repr) => repr.shared_size,
249        });
250        check_shared_memory(shared_bytes, props)
251    }
252
253    fn normalize_repr(
254        &self,
255        repr: Option<Self::Representation>,
256    ) -> (CompilerInfo, Option<AutoRepresentation>) {
257        let compiler_info = match &repr {
258            #[cfg(feature = "spirv")]
259            Some(AutoRepresentation::SpirV(repr)) => CompilerInfo::Vulkan {
260                params_transfer: match repr.immediate_size {
261                    Some(_) => ParamsTransfer::Immediate,
262                    None => ParamsTransfer::Uniform,
263                },
264            },
265            #[cfg(feature = "msl")]
266            Some(AutoRepresentation::Msl(_)) => CompilerInfo::Metal,
267            Some(AutoRepresentation::Wgsl(_)) => CompilerInfo::WGSL,
268            None => CompilerInfo::None,
269        };
270
271        (compiler_info, repr)
272    }
273}
274
275impl WgpuCompiler for WgslCompiler {
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        mode: ExecutionMode,
286    ) -> Result<CompiledKernel<Self>, CompilationError> {
287        kernel.compile(
288            definition,
289            self,
290            &server.compilation_options,
291            mode,
292            kernel.address_type(),
293        )
294    }
295
296    fn lang_tag(&self) -> &'static str {
297        "wgsl"
298    }
299
300    fn validate_ir(
301        &self,
302        repr: &Option<Self::Representation>,
303        props: &DeviceProperties,
304    ) -> Result<(), LaunchError> {
305        let shared_bytes = repr.as_ref().map(|repr| repr.shared_memory_bytes());
306        check_shared_memory(shared_bytes, props)
307    }
308
309    fn normalize_repr(
310        &self,
311        repr: Option<Self::Representation>,
312    ) -> (CompilerInfo, Option<AutoRepresentation>) {
313        (CompilerInfo::WGSL, repr.map(|r| r.into()))
314    }
315}
316
317#[cfg(feature = "msl")]
318impl WgpuCompiler for MslCompiler {
319    fn init(_backend: wgpu::Backend, _options: &WgpuCompilationOptions) -> Self {
320        Self::default()
321    }
322
323    fn compile_kernel(
324        &mut self,
325        _server: &mut WgpuServer<Self>,
326        kernel: <WgpuServer<Self> as ComputeServer>::Kernel,
327        definition: KernelDefinition,
328        mode: ExecutionMode,
329    ) -> Result<CompiledKernel<Self>, CompilationError> {
330        // The MSL compiler uses its own CompilationOptions, not WgpuCompilationOptions.
331        let compilation_options = cubecl_cpp::shared::CompilationOptions::default();
332        kernel.compile(
333            definition,
334            self,
335            &compilation_options,
336            mode,
337            kernel.address_type(),
338        )
339    }
340
341    fn lang_tag(&self) -> &'static str {
342        "msl"
343    }
344
345    fn validate_ir(
346        &self,
347        repr: &Option<Self::Representation>,
348        props: &DeviceProperties,
349    ) -> Result<(), LaunchError> {
350        let shared_bytes = repr.as_ref().map(|repr| repr.shared_memory_size());
351        check_shared_memory(shared_bytes, props)
352    }
353
354    fn normalize_repr(
355        &self,
356        repr: Option<Self::Representation>,
357    ) -> (CompilerInfo, Option<AutoRepresentation>) {
358        (CompilerInfo::Metal, repr.map(|r| r.into()))
359    }
360}
361
362#[cfg(feature = "spirv")]
363impl<T: cubecl_spirv::SpirvTarget> WgpuCompiler for cubecl_spirv::SpirvCompiler<T> {
364    fn init(_backend: wgpu::Backend, _options: &WgpuCompilationOptions) -> Self {
365        Self::default()
366    }
367
368    fn compile_kernel(
369        &mut self,
370        server: &mut WgpuServer<Self>,
371        kernel: <WgpuServer<Self> as ComputeServer>::Kernel,
372        definition: KernelDefinition,
373        mode: ExecutionMode,
374    ) -> Result<CompiledKernel<Self>, CompilationError> {
375        #[cfg(feature = "spirv-dump")]
376        let (name, id) = (kernel.name().to_string(), kernel.id());
377        let compiled = crate::vulkan::compile(self, server, kernel, definition, mode)?;
378        #[cfg(feature = "spirv-dump")]
379        if let Some(spirv) = compiled.repr.as_ref() {
380            crate::vulkan::dump_spirv(spirv, &name, id);
381        }
382        Ok(compiled)
383    }
384
385    fn lang_tag(&self) -> &'static str {
386        "spirv"
387    }
388
389    fn validate_ir(
390        &self,
391        repr: &Option<Self::Representation>,
392        props: &DeviceProperties,
393    ) -> Result<(), LaunchError> {
394        let shared_bytes = repr.as_ref().map(|repr| repr.shared_size);
395        check_shared_memory(shared_bytes, props)
396    }
397
398    fn normalize_repr(
399        &self,
400        repr: Option<Self::Representation>,
401    ) -> (CompilerInfo, Option<AutoRepresentation>) {
402        let params_transfer = match repr.as_ref().and_then(|r| r.immediate_size) {
403            Some(_) => ParamsTransfer::Immediate,
404            None => ParamsTransfer::Uniform,
405        };
406        (
407            CompilerInfo::Vulkan { params_transfer },
408            repr.map(|r| r.into()),
409        )
410    }
411}
412
413fn check_shared_memory(
414    shared_bytes: Option<usize>,
415    props: &DeviceProperties,
416) -> Result<(), LaunchError> {
417    let max_smem = props.hardware.max_shared_memory_size;
418    if let Some(shared_bytes) = shared_bytes
419        && shared_bytes > max_smem
420    {
421        return Err(ResourceLimitError::SharedMemory {
422            requested: shared_bytes,
423            max: max_smem,
424            backtrace: BackTrace::capture(),
425        }
426        .into());
427    }
428    Ok(())
429}
430
431/// Extension trait implemented by every compiler usable with the `wgpu` runtime.
432///
433/// The base [`Compiler`] trait already exposes a `compile` method that turns a
434/// [`KernelDefinition`] into a backend representation. [`WgpuCompiler`] sits one level
435/// higher: it owns the wgpu-specific lifecycle around a [`CubeTask`](cubecl_runtime::compiler::CubeTask)
436/// kernel — initializing the compiler for a given `wgpu::Backend`, compiling a kernel using
437/// the server's [`WgpuCompilationOptions`], validating the resulting IR against the device,
438/// and projecting the typed representation into the runtime-erased [`AutoRepresentation`].
439pub trait WgpuCompiler: Compiler {
440    /// Build the compiler instance appropriate for the given `wgpu` backend.
441    ///
442    /// `options` is consulted to decide between alternative implementations (for example, to
443    /// opt into the SPIR-V compiler on Vulkan when the device advertises the required
444    /// features).
445    fn init(backend: wgpu::Backend, options: &WgpuCompilationOptions) -> Self;
446
447    /// Validate that the compiled representation fits within the device's resource limits.
448    ///
449    /// Today this checks shared memory usage; additional checks may be added without
450    /// breaking the contract.
451    fn validate_ir(
452        &self,
453        repr: &Option<Self::Representation>,
454        props: &DeviceProperties,
455    ) -> Result<(), LaunchError>;
456
457    /// Compile a runtime kernel into a [`CompiledKernel`] ready for pipeline creation.
458    ///
459    /// Distinct from [`Compiler::compile`], which only translates a [`KernelDefinition`].
460    /// This entry point operates on a full server-level kernel and pulls compilation
461    /// options from `server`, so its signature cannot collide with the base trait method.
462    fn compile_kernel(
463        &mut self,
464        server: &mut WgpuServer<Self>,
465        kernel: <WgpuServer<Self> as ComputeServer>::Kernel,
466        definition: KernelDefinition,
467        mode: ExecutionMode,
468    ) -> Result<CompiledKernel<Self>, CompilationError>;
469
470    /// Short identifier of the shader language produced by this compiler (e.g. `"wgsl"`).
471    ///
472    /// Used for logging and debug-info tagging.
473    fn lang_tag(&self) -> &'static str;
474
475    /// Normalize the backend-specific representation into the [`AutoRepresentation`] shared
476    /// by every wgpu compiler, and report the [`CompilerInfo`] derived from it.
477    ///
478    /// The [`CompilerInfo`] tells the server which parameter-passing strategy to use for
479    /// the resulting pipeline.
480    fn normalize_repr(
481        &self,
482        repr: Option<Self::Representation>,
483    ) -> (CompilerInfo, Option<AutoRepresentation>);
484}