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, TargetProperties};
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};
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 fn limit_properties(&self, properties: &mut DeviceProperties) {
186 match self {
187 AutoCompiler::Wgsl(compiler) => compiler.limit_properties(properties),
188 #[cfg(feature = "spirv")]
189 AutoCompiler::SpirV(compiler) => compiler.limit_properties(properties),
190 #[cfg(feature = "msl")]
191 AutoCompiler::Msl(compiler) => compiler.limit_properties(properties),
192 }
193 }
194
195 fn target_properties(&self) -> TargetProperties {
196 match self {
197 AutoCompiler::Wgsl(compiler) => compiler.target_properties(),
198 #[cfg(feature = "spirv")]
199 AutoCompiler::SpirV(compiler) => compiler.target_properties(),
200 #[cfg(feature = "msl")]
201 AutoCompiler::Msl(compiler) => compiler.target_properties(),
202 }
203 }
204
205 #[cfg(feature = "msl")]
206 fn validate_runtime(
207 backend: wgpu::Backend,
208 options: &WgpuCompilationOptions,
209 requested_backend: WgpuBackend,
210 ) -> Result<(), WgpuCompilerInitError> {
211 if requested_backend == WgpuBackend::Metal {
212 MslCompiler::validate_runtime(backend, options, requested_backend)?;
213 }
214
215 Ok(())
216 }
217
218 fn init(backend: wgpu::Backend, options: &WgpuCompilationOptions) -> Self {
219 let _ = options; match backend {
221 #[cfg(feature = "spirv")]
222 wgpu::Backend::Vulkan if options.supports_vulkan_compiler => {
223 AutoCompiler::SpirV(Default::default())
224 }
225 #[cfg(feature = "msl")]
226 wgpu::Backend::Metal if options.supports_msl_compiler => {
227 AutoCompiler::Msl(Default::default())
228 }
229 _ => AutoCompiler::Wgsl(Default::default()),
230 }
231 }
232
233 fn compile_kernel(
234 &mut self,
235 kernel: &dyn CubeKernel,
236 definition: KernelDefinition,
237 options: &WgpuCompilationOptions,
238 ) -> Result<CompiledKernel<Self>, CompilationError> {
239 match self {
240 AutoCompiler::Wgsl(_) => CompiledKernel::compile(kernel, definition, self, options),
241 #[cfg(feature = "spirv")]
242 AutoCompiler::SpirV(_) => crate::vulkan::compile(self, kernel, definition, options),
243 #[cfg(feature = "msl")]
244 AutoCompiler::Msl(_) => CompiledKernel::compile(kernel, definition, self, options),
245 }
246 }
247
248 fn validate_ir(
249 &self,
250 repr: &Option<Self::Representation>,
251 props: &DeviceProperties,
252 ) -> Result<(), LaunchError> {
253 let shared_bytes = repr.as_ref().map(|repr| match repr {
254 AutoRepresentation::Wgsl(repr) => repr.shared_memory_size,
255 #[cfg(feature = "msl")]
256 AutoRepresentation::Msl(repr) => repr.shared_memory_size,
257 #[cfg(feature = "spirv")]
258 AutoRepresentation::SpirV(repr) => repr.shared_size,
259 });
260 check_shared_memory(shared_bytes, props)
261 }
262
263 fn normalize_repr(
264 &self,
265 repr: Option<Self::Representation>,
266 ) -> (CompilerInfo, Option<AutoRepresentation>) {
267 let compiler_info = match &repr {
268 #[cfg(feature = "spirv")]
269 Some(AutoRepresentation::SpirV(repr)) => CompilerInfo::Vulkan {
270 params_transfer: match repr.immediate_size {
271 Some(_) => ParamsTransfer::Immediate,
272 None => ParamsTransfer::Uniform,
273 },
274 },
275 #[cfg(feature = "msl")]
276 Some(AutoRepresentation::Msl(_)) => CompilerInfo::Metal,
277 Some(AutoRepresentation::Wgsl(_)) => CompilerInfo::WGSL,
278 None => CompilerInfo::None,
279 };
280
281 (compiler_info, repr)
282 }
283}
284
285impl WgpuCompiler for WgslCompiler {
286 fn limit_properties(&self, properties: &mut DeviceProperties) {
290 let hardware = &mut properties.hardware;
291 hardware.max_units_per_cube = hardware.max_units_per_cube.min(hardware.max_cube_dim.0);
292 }
293
294 fn init(_backend: wgpu::Backend, _options: &WgpuCompilationOptions) -> Self {
295 Self
296 }
297
298 fn compile_kernel(
299 &mut self,
300 kernel: &dyn CubeKernel,
301 definition: KernelDefinition,
302 options: &WgpuCompilationOptions,
303 ) -> Result<CompiledKernel<Self>, CompilationError> {
304 CompiledKernel::compile(kernel, definition, self, options)
305 }
306
307 fn validate_ir(
308 &self,
309 repr: &Option<Self::Representation>,
310 props: &DeviceProperties,
311 ) -> Result<(), LaunchError> {
312 let shared_bytes = repr.as_ref().map(|repr| repr.shared_memory_size);
313 check_shared_memory(shared_bytes, props)
314 }
315
316 fn normalize_repr(
317 &self,
318 repr: Option<Self::Representation>,
319 ) -> (CompilerInfo, Option<AutoRepresentation>) {
320 (CompilerInfo::WGSL, repr.map(|r| r.into()))
321 }
322}
323
324#[cfg(feature = "msl")]
325impl WgpuCompiler for MslCompiler {
326 fn validate_runtime(
327 backend: wgpu::Backend,
328 options: &WgpuCompilationOptions,
329 _requested_backend: WgpuBackend,
330 ) -> Result<(), WgpuCompilerInitError> {
331 if backend != wgpu::Backend::Metal {
332 return Err(WgpuCompilerInitError::BackendMismatch {
333 compiler: "MslCompiler",
334 expected: wgpu::Backend::Metal,
335 actual: backend,
336 });
337 }
338
339 if !options.supports_msl_compiler {
340 return Err(WgpuCompilerInitError::Unavailable {
341 compiler: "MslCompiler",
342 backend,
343 });
344 }
345
346 Ok(())
347 }
348
349 fn init(_backend: wgpu::Backend, _options: &WgpuCompilationOptions) -> Self {
350 Self::default()
351 }
352
353 fn target_properties(&self) -> TargetProperties {
354 TargetProperties {
355 mma: cubecl_cpp::metal::mma::mma_properties_metal(),
356 }
357 }
358
359 fn compile_kernel(
360 &mut self,
361 kernel: &dyn CubeKernel,
362 definition: KernelDefinition,
363 options: &WgpuCompilationOptions,
364 ) -> Result<CompiledKernel<Self>, CompilationError> {
365 let _ = options;
367 let compilation_options = cubecl_cpp::shared::CompilationOptions::default();
368 CompiledKernel::compile(kernel, definition, self, &compilation_options)
369 }
370
371 fn validate_ir(
372 &self,
373 repr: &Option<Self::Representation>,
374 props: &DeviceProperties,
375 ) -> Result<(), LaunchError> {
376 let shared_bytes = repr.as_ref().map(|repr| repr.shared_memory_size);
377 check_shared_memory(shared_bytes, props)
378 }
379
380 fn normalize_repr(
381 &self,
382 repr: Option<Self::Representation>,
383 ) -> (CompilerInfo, Option<AutoRepresentation>) {
384 (CompilerInfo::Metal, repr.map(|r| r.into()))
385 }
386}
387
388#[cfg(feature = "spirv")]
389impl WgpuCompiler for cubecl_spirv::SpirvCompiler {
390 fn init(_backend: wgpu::Backend, _options: &WgpuCompilationOptions) -> Self {
391 Self
392 }
393
394 fn compile_kernel(
395 &mut self,
396 kernel: &dyn CubeKernel,
397 definition: KernelDefinition,
398 options: &WgpuCompilationOptions,
399 ) -> Result<CompiledKernel<Self>, CompilationError> {
400 crate::vulkan::compile(self, kernel, definition, options)
401 }
402
403 fn validate_ir(
404 &self,
405 repr: &Option<Self::Representation>,
406 props: &DeviceProperties,
407 ) -> Result<(), LaunchError> {
408 let shared_bytes = repr.as_ref().map(|repr| repr.shared_size);
409 check_shared_memory(shared_bytes, props)
410 }
411
412 fn normalize_repr(
413 &self,
414 repr: Option<Self::Representation>,
415 ) -> (CompilerInfo, Option<AutoRepresentation>) {
416 let params_transfer = match repr.as_ref().and_then(|r| r.immediate_size) {
417 Some(_) => ParamsTransfer::Immediate,
418 None => ParamsTransfer::Uniform,
419 };
420 (
421 CompilerInfo::Vulkan { params_transfer },
422 repr.map(|r| r.into()),
423 )
424 }
425}
426
427fn check_shared_memory(
428 shared_bytes: Option<usize>,
429 props: &DeviceProperties,
430) -> Result<(), LaunchError> {
431 let max_smem = props.hardware.max_shared_memory_size;
432 if let Some(shared_bytes) = shared_bytes
433 && shared_bytes > max_smem
434 {
435 return Err(ResourceLimitError::SharedMemory {
436 requested: shared_bytes,
437 max: max_smem,
438 backtrace: BackTrace::capture(),
439 }
440 .into());
441 }
442 Ok(())
443}
444
445#[non_exhaustive]
447#[derive(Debug, Error, PartialEq, Eq)]
448pub enum WgpuCompilerInitError {
449 #[error("`{compiler}` requires the {expected:?} wgpu backend, but {actual:?} was selected")]
451 BackendMismatch {
452 compiler: &'static str,
453 expected: wgpu::Backend,
454 actual: wgpu::Backend,
455 },
456 #[error(
458 "`{compiler}` was requested explicitly, but it is unavailable on the selected {backend:?} device; use `AutoCompiler` with an automatic device to allow WGSL fallback"
459 )]
460 Unavailable {
461 compiler: &'static str,
462 backend: wgpu::Backend,
463 },
464}
465
466pub trait WgpuCompiler: Compiler<Representation: Send> {
478 fn validate_runtime(
486 _backend: wgpu::Backend,
487 _options: &WgpuCompilationOptions,
488 _requested_backend: WgpuBackend,
489 ) -> Result<(), WgpuCompilerInitError> {
490 Ok(())
491 }
492
493 fn init(backend: wgpu::Backend, options: &WgpuCompilationOptions) -> Self;
499
500 fn limit_properties(&self, _properties: &mut DeviceProperties) {}
503
504 fn target_properties(&self) -> TargetProperties {
508 TargetProperties::default()
509 }
510
511 fn validate_ir(
516 &self,
517 repr: &Option<Self::Representation>,
518 props: &DeviceProperties,
519 ) -> Result<(), LaunchError>;
520
521 fn compile_kernel(
527 &mut self,
528 kernel: &dyn CubeKernel,
529 definition: KernelDefinition,
530 options: &WgpuCompilationOptions,
531 ) -> Result<CompiledKernel<Self>, CompilationError>;
532
533 fn normalize_repr(
539 &self,
540 repr: Option<Self::Representation>,
541 ) -> (CompilerInfo, Option<AutoRepresentation>);
542}
543
544#[cfg(all(test, feature = "msl"))]
545mod tests {
546 use super::*;
547
548 #[test]
549 fn auto_compiler_falls_back_to_wgsl_when_msl_is_unavailable() {
550 let options = WgpuCompilationOptions::default();
551 assert!(
552 AutoCompiler::validate_runtime(wgpu::Backend::Metal, &options, WgpuBackend::Auto)
553 .is_ok()
554 );
555 let compiler = AutoCompiler::init(wgpu::Backend::Metal, &options);
556
557 assert!(matches!(compiler, AutoCompiler::Wgsl(_)));
558 }
559
560 #[test]
561 fn explicit_msl_compiler_rejects_wgsl_fallback() {
562 for requested_backend in [WgpuBackend::Auto, WgpuBackend::Metal] {
563 let result = MslCompiler::validate_runtime(
564 wgpu::Backend::Metal,
565 &WgpuCompilationOptions::default(),
566 requested_backend,
567 );
568
569 assert_eq!(
570 result,
571 Err(WgpuCompilerInitError::Unavailable {
572 compiler: "MslCompiler",
573 backend: wgpu::Backend::Metal,
574 })
575 );
576 }
577 }
578
579 #[test]
580 fn explicit_msl_compiler_rejects_non_metal_backend() {
581 let options = WgpuCompilationOptions {
582 supports_msl_compiler: true,
583 ..Default::default()
584 };
585 let result =
586 MslCompiler::validate_runtime(wgpu::Backend::Vulkan, &options, WgpuBackend::Auto);
587
588 assert_eq!(
589 result,
590 Err(WgpuCompilerInitError::BackendMismatch {
591 compiler: "MslCompiler",
592 expected: wgpu::Backend::Metal,
593 actual: wgpu::Backend::Vulkan,
594 })
595 );
596 }
597
598 #[test]
599 fn explicit_msl_compiler_accepts_supported_metal_device() {
600 let options = WgpuCompilationOptions {
601 supports_msl_compiler: true,
602 ..Default::default()
603 };
604
605 assert!(
606 MslCompiler::validate_runtime(wgpu::Backend::Metal, &options, WgpuBackend::Auto)
607 .is_ok()
608 );
609 }
610
611 #[test]
612 fn explicit_wgsl_on_metal_does_not_require_native_msl() {
613 for supports_msl_compiler in [false, true] {
614 let options = WgpuCompilationOptions {
615 supports_msl_compiler,
616 ..Default::default()
617 };
618 assert!(
619 WgslCompiler::validate_runtime(wgpu::Backend::Metal, &options, WgpuBackend::Metal)
620 .is_ok()
621 );
622 }
623 }
624
625 #[test]
626 fn auto_compiler_on_explicit_metal_requires_native_msl() {
627 let options = WgpuCompilationOptions::default();
628 assert_eq!(
629 AutoCompiler::validate_runtime(wgpu::Backend::Metal, &options, WgpuBackend::Metal),
630 Err(WgpuCompilerInitError::Unavailable {
631 compiler: "MslCompiler",
632 backend: wgpu::Backend::Metal,
633 })
634 );
635 let supported = WgpuCompilationOptions {
636 supports_msl_compiler: true,
637 ..options
638 };
639 assert!(
640 AutoCompiler::validate_runtime(wgpu::Backend::Metal, &supported, WgpuBackend::Metal)
641 .is_ok()
642 );
643 }
644}