Skip to main content

cubecl_wgpu/compiler/
base.rs

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