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;
16use thiserror::Error;
17
18#[cfg(feature = "spirv")]
19use crate::ParamsTransfer;
20use crate::{CompilerInfo, WgpuBackend, WgpuServer};
21
22use super::wgsl;
23
24#[cfg(feature = "msl")]
25pub use cubecl_cpp::MslCompiler;
26#[cfg(feature = "spirv")]
27pub use cubecl_spirv::SpirvCompiler;
28pub use wgsl::WgslCompiler;
29
30#[allow(clippy::large_enum_variant)]
36#[derive(Debug, Clone)]
37pub enum AutoCompiler {
38 Wgsl(WgslCompiler),
40 #[cfg(feature = "spirv")]
42 SpirV(cubecl_spirv::SpirvCompiler),
43 #[cfg(feature = "msl")]
45 Msl(MslCompiler),
46}
47
48#[derive(From)]
50#[allow(clippy::large_enum_variant)]
51pub enum AutoRepresentation {
52 Wgsl(wgsl::ComputeShader),
54 #[cfg(feature = "spirv")]
56 SpirV(cubecl_spirv::SpirvKernel),
57 #[cfg(feature = "msl")]
59 Msl(MslComputeKernel),
60}
61
62#[derive(From, Clone, Copy)]
64#[allow(clippy::large_enum_variant)]
65pub enum AutoRepresentationRef<'a> {
66 Wgsl(&'a wgsl::ComputeShader),
68 #[cfg(feature = "spirv")]
70 SpirV(&'a cubecl_spirv::SpirvKernel),
71 #[cfg(feature = "msl")]
73 Msl(&'a MslComputeKernel),
74}
75
76#[cfg(feature = "spirv")]
77impl AutoRepresentation {
78 pub fn as_spirv(&self) -> Option<&cubecl_spirv::SpirvKernel> {
80 match self {
81 AutoRepresentation::SpirV(repr) => Some(repr),
82 _ => None,
83 }
84 }
85}
86
87#[cfg(feature = "msl")]
88impl AutoRepresentation {
89 pub fn as_msl(&self) -> Option<&MslComputeKernel> {
91 match self {
92 AutoRepresentation::Msl(repr) => Some(repr),
93 _ => None,
94 }
95 }
96}
97
98impl AutoRepresentation {
99 pub fn as_ref(&self) -> AutoRepresentationRef<'_> {
101 match self {
102 AutoRepresentation::Wgsl(compute_shader) => AutoRepresentationRef::Wgsl(compute_shader),
103 #[cfg(feature = "spirv")]
104 AutoRepresentation::SpirV(spirv_kernel) => AutoRepresentationRef::SpirV(spirv_kernel),
105 #[cfg(feature = "msl")]
106 AutoRepresentation::Msl(compute_shader) => AutoRepresentationRef::Msl(compute_shader),
107 }
108 }
109}
110
111impl Display for AutoRepresentation {
112 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
113 match self {
114 AutoRepresentation::Wgsl(compute_shader) => compute_shader.fmt(f),
115 #[cfg(feature = "spirv")]
116 AutoRepresentation::SpirV(spirv_kernel) => spirv_kernel.fmt(f),
117 #[cfg(feature = "msl")]
118 AutoRepresentation::Msl(compute_shader) => compute_shader.fmt(f),
119 }
120 }
121}
122
123impl Compiler for AutoCompiler {
124 type Representation = AutoRepresentation;
125
126 type CompilationOptions = WgpuCompilationOptions;
127
128 fn compile(
129 &mut self,
130 kernel: KernelDefinition,
131 compilation_options: &Self::CompilationOptions,
132 ) -> Result<Self::Representation, CompilationError> {
133 let kernel = match self {
134 AutoCompiler::Wgsl(wgsl_compiler) => {
135 Compiler::compile(wgsl_compiler, kernel, compilation_options)?.into()
136 }
137 #[cfg(feature = "spirv")]
138 AutoCompiler::SpirV(spirv_compiler) => {
139 Compiler::compile(spirv_compiler, kernel, compilation_options)?.into()
140 }
141 #[cfg(feature = "msl")]
142 AutoCompiler::Msl(msl_compiler) => {
143 use cubecl_cpp;
145 let compilation_options = cubecl_cpp::shared::CompilationOptions::default();
146 Compiler::compile(msl_compiler, kernel, &compilation_options)?.into()
147 }
148 };
149
150 Ok(kernel)
151 }
152
153 fn buffer_io(repr: &Self::Representation) -> Option<Vec<BufferIOAttr>> {
154 match repr {
155 AutoRepresentation::Wgsl(shader) => WgslCompiler::buffer_io(shader),
156 #[cfg(feature = "spirv")]
157 AutoRepresentation::SpirV(kernel) => cubecl_spirv::SpirvCompiler::buffer_io(kernel),
158 #[cfg(feature = "msl")]
159 AutoRepresentation::Msl(kernel) => MslCompiler::buffer_io(kernel),
160 }
161 }
162
163 fn extension(&self) -> &'static str {
164 match self {
165 AutoCompiler::Wgsl(_) => "wgsl",
166 #[cfg(feature = "spirv")]
167 AutoCompiler::SpirV(_) => "spv",
168 #[cfg(feature = "msl")]
169 AutoCompiler::Msl(_) => "msl",
170 }
171 }
172
173 fn lang_tag(&self) -> &'static str {
174 match self {
175 AutoCompiler::Wgsl(compiler) => compiler.lang_tag(),
176 #[cfg(feature = "spirv")]
177 AutoCompiler::SpirV(compiler) => compiler.lang_tag(),
178 #[cfg(feature = "msl")]
179 AutoCompiler::Msl(compiler) => compiler.lang_tag(),
180 }
181 }
182}
183
184impl WgpuCompiler for AutoCompiler {
185 #[cfg(feature = "msl")]
186 fn validate_runtime(
187 backend: wgpu::Backend,
188 options: &WgpuCompilationOptions,
189 requested_backend: WgpuBackend,
190 ) -> Result<(), WgpuCompilerInitError> {
191 if requested_backend == WgpuBackend::Metal {
192 MslCompiler::validate_runtime(backend, options, requested_backend)?;
193 }
194
195 Ok(())
196 }
197
198 fn init(backend: wgpu::Backend, options: &WgpuCompilationOptions) -> Self {
199 let _ = options; match backend {
201 #[cfg(feature = "spirv")]
202 wgpu::Backend::Vulkan if options.supports_vulkan_compiler => {
203 AutoCompiler::SpirV(Default::default())
204 }
205 #[cfg(feature = "msl")]
206 wgpu::Backend::Metal if options.supports_msl_compiler => {
207 AutoCompiler::Msl(Default::default())
208 }
209 _ => AutoCompiler::Wgsl(Default::default()),
210 }
211 }
212
213 fn compile_kernel(
214 &mut self,
215 server: &mut WgpuServer<AutoCompiler>,
216 kernel: Box<dyn CubeKernel>,
217 definition: KernelDefinition,
218 ) -> Result<CompiledKernel<Self>, CompilationError> {
219 match self {
220 AutoCompiler::Wgsl(_) => {
221 CompiledKernel::compile(&*kernel, definition, self, &server.compilation_options)
222 }
223 #[cfg(feature = "spirv")]
224 AutoCompiler::SpirV(_) => crate::vulkan::compile(self, server, kernel, definition),
225 #[cfg(feature = "msl")]
226 AutoCompiler::Msl(_) => {
227 CompiledKernel::compile(&*kernel, definition, self, &server.compilation_options)
228 }
229 }
230 }
231
232 fn validate_ir(
233 &self,
234 repr: &Option<Self::Representation>,
235 props: &DeviceProperties,
236 ) -> Result<(), LaunchError> {
237 let shared_bytes = repr.as_ref().map(|repr| match repr {
238 AutoRepresentation::Wgsl(repr) => repr.shared_memory_size,
239 #[cfg(feature = "msl")]
240 AutoRepresentation::Msl(repr) => repr.shared_memory_size,
241 #[cfg(feature = "spirv")]
242 AutoRepresentation::SpirV(repr) => repr.shared_size,
243 });
244 check_shared_memory(shared_bytes, props)
245 }
246
247 fn normalize_repr(
248 &self,
249 repr: Option<Self::Representation>,
250 ) -> (CompilerInfo, Option<AutoRepresentation>) {
251 let compiler_info = match &repr {
252 #[cfg(feature = "spirv")]
253 Some(AutoRepresentation::SpirV(repr)) => CompilerInfo::Vulkan {
254 params_transfer: match repr.immediate_size {
255 Some(_) => ParamsTransfer::Immediate,
256 None => ParamsTransfer::Uniform,
257 },
258 },
259 #[cfg(feature = "msl")]
260 Some(AutoRepresentation::Msl(_)) => CompilerInfo::Metal,
261 Some(AutoRepresentation::Wgsl(_)) => CompilerInfo::WGSL,
262 None => CompilerInfo::None,
263 };
264
265 (compiler_info, repr)
266 }
267}
268
269impl WgpuCompiler for WgslCompiler {
270 fn init(_backend: wgpu::Backend, _options: &WgpuCompilationOptions) -> Self {
271 Self
272 }
273
274 fn compile_kernel(
275 &mut self,
276 server: &mut WgpuServer<Self>,
277 kernel: Box<dyn CubeKernel>,
278 definition: KernelDefinition,
279 ) -> Result<CompiledKernel<Self>, CompilationError> {
280 CompiledKernel::compile(&*kernel, definition, self, &server.compilation_options)
281 }
282
283 fn validate_ir(
284 &self,
285 repr: &Option<Self::Representation>,
286 props: &DeviceProperties,
287 ) -> Result<(), LaunchError> {
288 let shared_bytes = repr.as_ref().map(|repr| repr.shared_memory_size);
289 check_shared_memory(shared_bytes, props)
290 }
291
292 fn normalize_repr(
293 &self,
294 repr: Option<Self::Representation>,
295 ) -> (CompilerInfo, Option<AutoRepresentation>) {
296 (CompilerInfo::WGSL, repr.map(|r| r.into()))
297 }
298}
299
300#[cfg(feature = "msl")]
301impl WgpuCompiler for MslCompiler {
302 fn validate_runtime(
303 backend: wgpu::Backend,
304 options: &WgpuCompilationOptions,
305 _requested_backend: WgpuBackend,
306 ) -> Result<(), WgpuCompilerInitError> {
307 if backend != wgpu::Backend::Metal {
308 return Err(WgpuCompilerInitError::BackendMismatch {
309 compiler: "MslCompiler",
310 expected: wgpu::Backend::Metal,
311 actual: backend,
312 });
313 }
314
315 if !options.supports_msl_compiler {
316 return Err(WgpuCompilerInitError::Unavailable {
317 compiler: "MslCompiler",
318 backend,
319 });
320 }
321
322 Ok(())
323 }
324
325 fn init(_backend: wgpu::Backend, _options: &WgpuCompilationOptions) -> Self {
326 Self::default()
327 }
328
329 fn compile_kernel(
330 &mut self,
331 _server: &mut WgpuServer<Self>,
332 kernel: Box<dyn CubeKernel>,
333 definition: KernelDefinition,
334 ) -> Result<CompiledKernel<Self>, CompilationError> {
335 let compilation_options = cubecl_cpp::shared::CompilationOptions::default();
337 CompiledKernel::compile(&*kernel, definition, self, &compilation_options)
338 }
339
340 fn validate_ir(
341 &self,
342 repr: &Option<Self::Representation>,
343 props: &DeviceProperties,
344 ) -> Result<(), LaunchError> {
345 let shared_bytes = repr.as_ref().map(|repr| repr.shared_memory_size);
346 check_shared_memory(shared_bytes, props)
347 }
348
349 fn normalize_repr(
350 &self,
351 repr: Option<Self::Representation>,
352 ) -> (CompilerInfo, Option<AutoRepresentation>) {
353 (CompilerInfo::Metal, repr.map(|r| r.into()))
354 }
355}
356
357#[cfg(feature = "spirv")]
358impl WgpuCompiler for cubecl_spirv::SpirvCompiler {
359 fn init(_backend: wgpu::Backend, _options: &WgpuCompilationOptions) -> Self {
360 Self
361 }
362
363 fn compile_kernel(
364 &mut self,
365 server: &mut WgpuServer<Self>,
366 kernel: Box<dyn CubeKernel>,
367 definition: KernelDefinition,
368 ) -> Result<CompiledKernel<Self>, CompilationError> {
369 crate::vulkan::compile(self, server, kernel, definition)
370 }
371
372 fn validate_ir(
373 &self,
374 repr: &Option<Self::Representation>,
375 props: &DeviceProperties,
376 ) -> Result<(), LaunchError> {
377 let shared_bytes = repr.as_ref().map(|repr| repr.shared_size);
378 check_shared_memory(shared_bytes, props)
379 }
380
381 fn normalize_repr(
382 &self,
383 repr: Option<Self::Representation>,
384 ) -> (CompilerInfo, Option<AutoRepresentation>) {
385 let params_transfer = match repr.as_ref().and_then(|r| r.immediate_size) {
386 Some(_) => ParamsTransfer::Immediate,
387 None => ParamsTransfer::Uniform,
388 };
389 (
390 CompilerInfo::Vulkan { params_transfer },
391 repr.map(|r| r.into()),
392 )
393 }
394}
395
396fn check_shared_memory(
397 shared_bytes: Option<usize>,
398 props: &DeviceProperties,
399) -> Result<(), LaunchError> {
400 let max_smem = props.hardware.max_shared_memory_size;
401 if let Some(shared_bytes) = shared_bytes
402 && shared_bytes > max_smem
403 {
404 return Err(ResourceLimitError::SharedMemory {
405 requested: shared_bytes,
406 max: max_smem,
407 backtrace: BackTrace::capture(),
408 }
409 .into());
410 }
411 Ok(())
412}
413
414#[non_exhaustive]
416#[derive(Debug, Error, PartialEq, Eq)]
417pub enum WgpuCompilerInitError {
418 #[error("`{compiler}` requires the {expected:?} wgpu backend, but {actual:?} was selected")]
420 BackendMismatch {
421 compiler: &'static str,
422 expected: wgpu::Backend,
423 actual: wgpu::Backend,
424 },
425 #[error(
427 "`{compiler}` was requested explicitly, but it is unavailable on the selected {backend:?} device; use `AutoCompiler` with an automatic device to allow WGSL fallback"
428 )]
429 Unavailable {
430 compiler: &'static str,
431 backend: wgpu::Backend,
432 },
433}
434
435pub trait WgpuCompiler: Compiler {
444 fn validate_runtime(
452 _backend: wgpu::Backend,
453 _options: &WgpuCompilationOptions,
454 _requested_backend: WgpuBackend,
455 ) -> Result<(), WgpuCompilerInitError> {
456 Ok(())
457 }
458
459 fn init(backend: wgpu::Backend, options: &WgpuCompilationOptions) -> Self;
465
466 fn validate_ir(
471 &self,
472 repr: &Option<Self::Representation>,
473 props: &DeviceProperties,
474 ) -> Result<(), LaunchError>;
475
476 fn compile_kernel(
482 &mut self,
483 server: &mut WgpuServer<Self>,
484 kernel: Box<dyn CubeKernel>,
485 definition: KernelDefinition,
486 ) -> Result<CompiledKernel<Self>, CompilationError>;
487
488 fn normalize_repr(
494 &self,
495 repr: Option<Self::Representation>,
496 ) -> (CompilerInfo, Option<AutoRepresentation>);
497}
498
499#[cfg(all(test, feature = "msl"))]
500mod tests {
501 use super::*;
502
503 #[test]
504 fn auto_compiler_falls_back_to_wgsl_when_msl_is_unavailable() {
505 let options = WgpuCompilationOptions::default();
506 assert!(
507 AutoCompiler::validate_runtime(wgpu::Backend::Metal, &options, WgpuBackend::Auto)
508 .is_ok()
509 );
510 let compiler = AutoCompiler::init(wgpu::Backend::Metal, &options);
511
512 assert!(matches!(compiler, AutoCompiler::Wgsl(_)));
513 }
514
515 #[test]
516 fn explicit_msl_compiler_rejects_wgsl_fallback() {
517 for requested_backend in [WgpuBackend::Auto, WgpuBackend::Metal] {
518 let result = MslCompiler::validate_runtime(
519 wgpu::Backend::Metal,
520 &WgpuCompilationOptions::default(),
521 requested_backend,
522 );
523
524 assert_eq!(
525 result,
526 Err(WgpuCompilerInitError::Unavailable {
527 compiler: "MslCompiler",
528 backend: wgpu::Backend::Metal,
529 })
530 );
531 }
532 }
533
534 #[test]
535 fn explicit_msl_compiler_rejects_non_metal_backend() {
536 let options = WgpuCompilationOptions {
537 supports_msl_compiler: true,
538 ..Default::default()
539 };
540 let result =
541 MslCompiler::validate_runtime(wgpu::Backend::Vulkan, &options, WgpuBackend::Auto);
542
543 assert_eq!(
544 result,
545 Err(WgpuCompilerInitError::BackendMismatch {
546 compiler: "MslCompiler",
547 expected: wgpu::Backend::Metal,
548 actual: wgpu::Backend::Vulkan,
549 })
550 );
551 }
552
553 #[test]
554 fn explicit_msl_compiler_accepts_supported_metal_device() {
555 let options = WgpuCompilationOptions {
556 supports_msl_compiler: true,
557 ..Default::default()
558 };
559
560 assert!(
561 MslCompiler::validate_runtime(wgpu::Backend::Metal, &options, WgpuBackend::Auto)
562 .is_ok()
563 );
564 }
565
566 #[test]
567 fn explicit_wgsl_on_metal_does_not_require_native_msl() {
568 for supports_msl_compiler in [false, true] {
569 let options = WgpuCompilationOptions {
570 supports_msl_compiler,
571 ..Default::default()
572 };
573 assert!(
574 WgslCompiler::validate_runtime(wgpu::Backend::Metal, &options, WgpuBackend::Metal)
575 .is_ok()
576 );
577 }
578 }
579
580 #[test]
581 fn auto_compiler_on_explicit_metal_requires_native_msl() {
582 let options = WgpuCompilationOptions::default();
583 assert_eq!(
584 AutoCompiler::validate_runtime(wgpu::Backend::Metal, &options, WgpuBackend::Metal),
585 Err(WgpuCompilerInitError::Unavailable {
586 compiler: "MslCompiler",
587 backend: wgpu::Backend::Metal,
588 })
589 );
590 let supported = WgpuCompilationOptions {
591 supports_msl_compiler: true,
592 ..options
593 };
594 assert!(
595 AutoCompiler::validate_runtime(wgpu::Backend::Metal, &supported, WgpuBackend::Metal)
596 .is_ok()
597 );
598 }
599}