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, 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/// Compiler that dispatches to the most appropriate shader language backend for the active
31/// `wgpu` backend.
32///
33/// The variant is selected at runtime by [`WgpuCompiler::init`] based on the `wgpu::Backend`
34/// in use and the enabled cargo features (`spirv`, `msl`).
35#[allow(clippy::large_enum_variant)]
36#[derive(Debug, Clone)]
37pub enum AutoCompiler {
38    /// WGSL backend, available on every `wgpu` backend.
39    Wgsl(WgslCompiler),
40    /// SPIR-V backend used on Vulkan when the device supports the required features.
41    #[cfg(feature = "spirv")]
42    SpirV(cubecl_spirv::SpirvCompiler),
43    /// Metal Shading Language backend used on the Metal backend.
44    #[cfg(feature = "msl")]
45    Msl(MslCompiler),
46}
47
48/// Owned compiled kernel representation matching the variants of [`AutoCompiler`].
49#[derive(From)]
50#[allow(clippy::large_enum_variant)]
51pub enum AutoRepresentation {
52    /// WGSL compute shader source.
53    Wgsl(wgsl::ComputeShader),
54    /// Compiled SPIR-V kernel.
55    #[cfg(feature = "spirv")]
56    SpirV(cubecl_spirv::SpirvKernel),
57    /// Compiled Metal Shading Language kernel.
58    #[cfg(feature = "msl")]
59    Msl(MslComputeKernel),
60}
61
62/// Borrowed counterpart of [`AutoRepresentation`], useful when only read access is needed.
63#[derive(From, Clone, Copy)]
64#[allow(clippy::large_enum_variant)]
65pub enum AutoRepresentationRef<'a> {
66    /// Borrowed WGSL compute shader.
67    Wgsl(&'a wgsl::ComputeShader),
68    /// Borrowed SPIR-V kernel.
69    #[cfg(feature = "spirv")]
70    SpirV(&'a cubecl_spirv::SpirvKernel),
71    /// Borrowed Metal Shading Language kernel.
72    #[cfg(feature = "msl")]
73    Msl(&'a MslComputeKernel),
74}
75
76#[cfg(feature = "spirv")]
77impl AutoRepresentation {
78    /// Returns the SPIR-V kernel if this representation is the SPIR-V variant.
79    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    /// Returns the MSL kernel if this representation is the MSL variant.
90    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    /// Borrow this representation as an [`AutoRepresentationRef`].
100    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                // override compilation options with cpp compiler options for metal
144                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; // Unused without `spirv` feature
220        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    /// A cube whose kernel reads its plane ids launches as one row along `x`
287    /// ([`WorkgroupLayout::Flat`](wgsl::builtin::WorkgroupLayout::Flat)), and the properties
288    /// cannot depend on the kernel, so every cube holds no more units than that row does.
289    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        // The MSL compiler uses its own CompilationOptions, not WgpuCompilationOptions.
366        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/// An explicitly selected wgpu compiler cannot serve the selected graphics backend or device.
446#[non_exhaustive]
447#[derive(Debug, Error, PartialEq, Eq)]
448pub enum WgpuCompilerInitError {
449    /// The compiler only emits shaders for a different graphics backend.
450    #[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    /// The selected device does not meet the compiler's requirements.
457    #[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
466/// Extension trait implemented by every compiler usable with the `wgpu` runtime.
467///
468/// The base [`Compiler`] trait already exposes a `compile` method that turns a
469/// [`KernelDefinition`] into a backend representation. [`WgpuCompiler`] sits one level
470/// higher: it owns the wgpu-specific lifecycle around a [`CubeKernel`](cubecl_server::kernel::CubeKernel)
471/// kernel — initializing the compiler for a given `wgpu::Backend`, compiling a kernel using
472/// the server's [`WgpuCompilationOptions`], validating the resulting IR against the device,
473/// and projecting the typed representation into the runtime-erased [`AutoRepresentation`].
474///
475/// Its representation is `Send` so a compiled kernel can travel to the thread
476/// that builds its pipeline.
477pub trait WgpuCompiler: Compiler<Representation: Send> {
478    /// Check that this compiler can serve the selected graphics backend and device.
479    ///
480    /// Explicit compilers should return an error instead of silently selecting another shader
481    /// language. `requested_backend` preserves the unresolved selection; [`WgpuBackend::Auto`]
482    /// allows automatic API selection. With `msl` enabled, [`AutoCompiler`] requires native MSL
483    /// for an explicit Metal request; otherwise it permits WGSL fallback. Explicit compilers apply
484    /// their own requirements.
485    fn validate_runtime(
486        _backend: wgpu::Backend,
487        _options: &WgpuCompilationOptions,
488        _requested_backend: WgpuBackend,
489    ) -> Result<(), WgpuCompilerInitError> {
490        Ok(())
491    }
492
493    /// Build the compiler instance appropriate for the given `wgpu` backend.
494    ///
495    /// `options` is consulted to decide between alternative implementations (for example, to
496    /// opt into the SPIR-V compiler on Vulkan when the device advertises the required
497    /// features).
498    fn init(backend: wgpu::Backend, options: &WgpuCompilationOptions) -> Self;
499
500    /// Narrow the device's `properties` to what this compiler's kernels can reach. Most
501    /// compilers reach the device's own limits and leave them as they are.
502    fn limit_properties(&self, _properties: &mut DeviceProperties) {}
503
504    /// What a kernel reads of this compiler's target as it expands: how a manual MMA's
505    /// registers map onto its matrices. Only MSL lowers a manual MMA; the others keep the
506    /// defaults, which nothing reads.
507    fn target_properties(&self) -> TargetProperties {
508        TargetProperties::default()
509    }
510
511    /// Validate that the compiled representation fits within the device's resource limits.
512    ///
513    /// Today this checks shared memory usage; additional checks may be added without
514    /// breaking the contract.
515    fn validate_ir(
516        &self,
517        repr: &Option<Self::Representation>,
518        props: &DeviceProperties,
519    ) -> Result<(), LaunchError>;
520
521    /// Compile a runtime kernel into a [`CompiledKernel`] ready for pipeline creation.
522    ///
523    /// Distinct from [`Compiler::compile`], which only translates a [`KernelDefinition`].
524    /// This entry point operates on a full server-level kernel and pulls compilation
525    /// options from `server`, so its signature cannot collide with the base trait method.
526    fn compile_kernel(
527        &mut self,
528        kernel: &dyn CubeKernel,
529        definition: KernelDefinition,
530        options: &WgpuCompilationOptions,
531    ) -> Result<CompiledKernel<Self>, CompilationError>;
532
533    /// Normalize the backend-specific representation into the [`AutoRepresentation`] shared
534    /// by every wgpu compiler, and report the [`CompilerInfo`] derived from it.
535    ///
536    /// The [`CompilerInfo`] tells the server which parameter-passing strategy to use for
537    /// the resulting pipeline.
538    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}