rustc_codegen_spirv/
lib.rs

1// HACK(eddyb) start of `rustc_codegen_ssa` crate-level attributes (see `build.rs`).
2#![allow(internal_features)]
3#![allow(rustc::diagnostic_outside_of_impl)]
4#![allow(rustc::untranslatable_diagnostic)]
5#![feature(assert_matches)]
6#![feature(box_patterns)]
7#![feature(file_buffered)]
8#![feature(if_let_guard)]
9#![feature(negative_impls)]
10#![feature(rustdoc_internals)]
11#![feature(string_from_utf8_lossy_owned)]
12#![feature(trait_alias)]
13#![feature(try_blocks)]
14#![recursion_limit = "256"]
15// HACK(eddyb) end of `rustc_codegen_ssa` crate-level attributes (see `build.rs`).
16
17//! Welcome to the API documentation for the `rust-gpu` project, this API is
18//! unstable and mainly intended for developing on the project itself. This is
19//! the API documentation for `rustc_codegen_spirv` which is not that useful on
20//! its own. You might also be interested in the following crates. There's also
21//! the [Rust GPU Dev Guide][gpu-dev-guide] which contains more user-level
22//! information on how to use and setup `rust-gpu`.
23//!
24//! - [`spirv-builder`]
25//! - [`spirv-std`]
26//! - [`spirv-tools`]
27//! - [`spirv-tools-sys`]
28//!
29//! [gpu-dev-guide]: https://rust-gpu.github.io/rust-gpu/book
30//! [`spirv-builder`]: https://rust-gpu.github.io/rust-gpu/api/spirv_builder
31//! [`spirv-std`]: https://rust-gpu.github.io/rust-gpu/api/spirv_std
32//! [`spirv-tools`]: https://rust-gpu.github.io/rust-gpu/api/spirv_tools
33//! [`spirv-tools-sys`]: https://rust-gpu.github.io/rust-gpu/api/spirv_tools_sys
34#![feature(rustc_private)]
35// crate-specific exceptions:
36#![allow(
37    unsafe_code,                // rustc_codegen_ssa requires unsafe functions in traits to be impl'd
38    clippy::enum_glob_use,      // pretty useful pattern with some codegen'd enums (e.g. rspirv::spirv::Op)
39    clippy::todo,               // still lots to implement :)
40
41    // FIXME(eddyb) new warnings from 1.83 rustup, apply their suggested changes.
42    mismatched_lifetime_syntaxes,
43    clippy::needless_lifetimes,
44)]
45
46// Unfortunately, this will not fail fast when compiling, but rather will wait for
47// rustc_codegen_spirv to be compiled. Putting this in build.rs will solve that problem, however,
48// that creates the much worse problem that then running `cargo check` will cause
49// rustc_codegen_spirv to be *compiled* instead of merely checked, something that takes
50// significantly longer. So, the trade-off between detecting a bad configuration slower for a
51// faster `cargo check` is worth it.
52#[cfg(all(feature = "use-compiled-tools", feature = "use-installed-tools"))]
53compile_error!(
54    "Either \"use-compiled-tools\" (enabled by default) or \"use-installed-tools\" may be enabled."
55);
56
57// HACK(eddyb) `build.rs` copies `rustc_codegen_ssa` (from the `rustc-dev` component)
58// and patches it to produce a "pqp" ("pre-`qptr`-patched") version that maintains
59// compatibility with "legacy" Rust-GPU pointer handling (mainly typed `alloca`s).
60//
61// FIXME(eddyb) get rid of this as soon as it's not needed anymore.
62#[cfg(not(rustc_codegen_spirv_disable_pqp_cg_ssa))]
63include!(concat!(env!("OUT_DIR"), "/pqp_cg_ssa.rs"));
64
65// HACK(eddyb) guide `rustc` to finding the right deps in the sysroot, which
66// (sadly) has to be outside `include!` to have any effect whatsoever.
67// FIXME(eddyb) this only really handles `bitflags`, not `object`.
68#[cfg(not(rustc_codegen_spirv_disable_pqp_cg_ssa))]
69mod _rustc_codegen_ssa_transitive_deps_hack {
70    extern crate rustc_codegen_ssa as _;
71}
72
73// NOTE(eddyb) `mod maybe_pqp_cg_ssa` is defined by the above `include`, when
74// in the (default for now) `pqp_cg_ssa` mode (see `build.rs`).
75#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]
76use rustc_codegen_ssa as maybe_pqp_cg_ssa;
77
78// FIXME(eddyb) remove all `#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]`
79// as soon as they're not needed anymore (i.e. using `rustc_codegen_ssa` again).
80#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]
81extern crate rustc_abi;
82extern crate rustc_apfloat;
83#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]
84extern crate rustc_arena;
85#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]
86extern crate rustc_ast;
87#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]
88extern crate rustc_attr_data_structures;
89#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]
90extern crate rustc_attr_parsing;
91#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]
92extern crate rustc_codegen_ssa;
93#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]
94extern crate rustc_data_structures;
95extern crate rustc_driver;
96#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]
97extern crate rustc_errors;
98#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]
99extern crate rustc_hashes;
100#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]
101extern crate rustc_hir;
102#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]
103extern crate rustc_index;
104extern crate rustc_interface;
105#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]
106extern crate rustc_metadata;
107#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]
108extern crate rustc_middle;
109#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]
110extern crate rustc_session;
111#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]
112extern crate rustc_span;
113#[cfg(rustc_codegen_spirv_disable_pqp_cg_ssa)]
114extern crate rustc_target;
115
116macro_rules! assert_ty_eq {
117    ($codegen_cx:expr, $left:expr, $right:expr) => {
118        assert!(
119            $left == $right,
120            "Expected types to be equal:\n{}\n==\n{}",
121            $codegen_cx.debug_type($left),
122            $codegen_cx.debug_type($right)
123        )
124    };
125}
126
127mod abi;
128mod attr;
129mod builder;
130mod builder_spirv;
131mod codegen_cx;
132mod custom_decorations;
133mod custom_insts;
134mod link;
135mod linker;
136mod spirv_type;
137mod spirv_type_constraints;
138mod symbols;
139mod target;
140mod target_feature;
141
142use builder::Builder;
143use codegen_cx::CodegenCx;
144use maybe_pqp_cg_ssa::back::lto::{LtoModuleCodegen, SerializedModule, ThinModule};
145use maybe_pqp_cg_ssa::back::write::{
146    CodegenContext, FatLtoInput, ModuleConfig, OngoingCodegen, TargetMachineFactoryConfig,
147};
148use maybe_pqp_cg_ssa::base::maybe_create_entry_wrapper;
149use maybe_pqp_cg_ssa::mono_item::MonoItemExt;
150use maybe_pqp_cg_ssa::traits::{
151    CodegenBackend, ExtraBackendMethods, ModuleBufferMethods, ThinBufferMethods,
152    WriteBackendMethods,
153};
154use maybe_pqp_cg_ssa::{CodegenResults, CompiledModule, ModuleCodegen, ModuleKind, TargetConfig};
155use rspirv::binary::Assemble;
156use rustc_ast::expand::allocator::AllocatorKind;
157use rustc_ast::expand::autodiff_attrs::AutoDiffItem;
158use rustc_data_structures::fx::FxIndexMap;
159use rustc_errors::{DiagCtxtHandle, FatalError};
160use rustc_metadata::EncodedMetadata;
161use rustc_middle::dep_graph::{WorkProduct, WorkProductId};
162use rustc_middle::mir::mono::{MonoItem, MonoItemData};
163use rustc_middle::mir::pretty::write_mir_pretty;
164use rustc_middle::ty::print::with_no_trimmed_paths;
165use rustc_middle::ty::{InstanceKind, TyCtxt};
166use rustc_session::Session;
167use rustc_session::config::{self, OutputFilenames, OutputType};
168use rustc_span::symbol::Symbol;
169use std::any::Any;
170use std::fs;
171use std::io::Cursor;
172use std::io::Write;
173use std::path::Path;
174use std::sync::Arc;
175use tracing::{error, warn};
176
177fn dump_mir(tcx: TyCtxt<'_>, mono_items: &[(MonoItem<'_>, MonoItemData)], path: &Path) {
178    fs::create_dir_all(path.parent().unwrap()).unwrap();
179    let mut file = fs::File::create(path).unwrap();
180    for &(mono_item, _) in mono_items {
181        if let MonoItem::Fn(instance) = mono_item
182            && matches!(instance.def, InstanceKind::Item(_))
183        {
184            let mut mir = Cursor::new(Vec::new());
185            if write_mir_pretty(tcx, Some(instance.def_id()), &mut mir).is_ok() {
186                writeln!(file, "{}", String::from_utf8(mir.into_inner()).unwrap()).unwrap();
187            }
188        }
189    }
190}
191
192#[derive(Clone)]
193struct SpirvCodegenBackend;
194
195impl CodegenBackend for SpirvCodegenBackend {
196    fn init(&self, sess: &Session) {
197        // Set up logging/tracing. See https://github.com/Rust-GPU/rust-gpu/issues/192.
198        init_logging(sess);
199    }
200
201    fn locale_resource(&self) -> &'static str {
202        rustc_errors::DEFAULT_LOCALE_RESOURCE
203    }
204
205    fn target_config(&self, sess: &Session) -> TargetConfig {
206        let cmdline = sess.opts.cg.target_feature.split(',');
207        let cfg = sess.target.options.features.split(',');
208
209        let target_features: Vec<_> = cfg
210            .chain(cmdline)
211            .filter(|l| l.starts_with('+'))
212            .map(|l| &l[1..])
213            .filter(|l| !l.is_empty())
214            .map(Symbol::intern)
215            .collect();
216
217        // HACK(eddyb) this should be a superset of `target_features`,
218        // which *additionally* also includes unstable target features,
219        // but there is no reason to make a distinction for SPIR-V ones.
220        let unstable_target_features = target_features.clone();
221
222        TargetConfig {
223            target_features,
224            unstable_target_features,
225
226            // FIXME(eddyb) support and/or emulate `f16` and `f128`.
227            has_reliable_f16: false,
228            has_reliable_f16_math: false,
229            has_reliable_f128: false,
230            has_reliable_f128_math: false,
231        }
232    }
233
234    fn provide(&self, providers: &mut rustc_middle::util::Providers) {
235        // FIXME(eddyb) this is currently only passed back to us, specifically
236        // into `target_machine_factory` (which is a noop), but it might make
237        // sense to move some of the target feature parsing into here.
238        providers.global_backend_features = |_tcx, ()| vec![];
239
240        crate::abi::provide(providers);
241        crate::attr::provide(providers);
242    }
243
244    fn codegen_crate(&self, tcx: TyCtxt<'_>) -> Box<dyn Any> {
245        Box::new(maybe_pqp_cg_ssa::base::codegen_crate(
246            Self,
247            tcx,
248            tcx.sess
249                .opts
250                .cg
251                .target_cpu
252                .clone()
253                .unwrap_or_else(|| tcx.sess.target.cpu.to_string()),
254        ))
255    }
256
257    fn join_codegen(
258        &self,
259        ongoing_codegen: Box<dyn Any>,
260        sess: &Session,
261        _outputs: &OutputFilenames,
262    ) -> (CodegenResults, FxIndexMap<WorkProductId, WorkProduct>) {
263        ongoing_codegen
264            .downcast::<OngoingCodegen<Self>>()
265            .expect("Expected OngoingCodegen, found Box<Any>")
266            .join(sess)
267    }
268
269    fn link(
270        &self,
271        sess: &Session,
272        codegen_results: CodegenResults,
273        metadata: EncodedMetadata,
274        outputs: &OutputFilenames,
275    ) {
276        let timer = sess.timer("link_crate");
277        link::link(
278            sess,
279            &codegen_results,
280            &metadata,
281            outputs,
282            codegen_results.crate_info.local_crate_name.as_str(),
283        );
284        drop(timer);
285    }
286}
287
288struct SpirvModuleBuffer(Vec<u32>);
289
290impl SpirvModuleBuffer {
291    fn as_bytes(&self) -> &[u8] {
292        spirv_tools::binary::from_binary(&self.0)
293    }
294}
295impl ModuleBufferMethods for SpirvModuleBuffer {
296    fn data(&self) -> &[u8] {
297        self.as_bytes()
298    }
299}
300impl ThinBufferMethods for SpirvModuleBuffer {
301    fn data(&self) -> &[u8] {
302        self.as_bytes()
303    }
304    fn thin_link_data(&self) -> &[u8] {
305        &[]
306    }
307}
308
309impl SpirvCodegenBackend {
310    fn optimize_common(
311        _cgcx: &CodegenContext<Self>,
312        module: &mut ModuleCodegen<<Self as WriteBackendMethods>::Module>,
313    ) -> Result<(), FatalError> {
314        // Apply DCE ("dead code elimination") to modules before ever serializing
315        // them as `.spv` files (technically, `.rcgu.o` files inside `.rlib`s),
316        // that will later get linked (potentially many times, esp. if this is
317        // some big upstream library, e.g. `core` itself), and will therefore
318        // benefit from not having to clean up all sorts of unreachable helpers.
319        linker::dce::dce(&mut module.module_llvm);
320
321        // FIXME(eddyb) run as many optimization passes as possible, not just DCE.
322
323        Ok(())
324    }
325}
326
327impl WriteBackendMethods for SpirvCodegenBackend {
328    type Module = rspirv::dr::Module;
329    type TargetMachine = ();
330    type TargetMachineError = String;
331    type ModuleBuffer = SpirvModuleBuffer;
332    type ThinData = ();
333    type ThinBuffer = SpirvModuleBuffer;
334
335    // FIXME(eddyb) reuse the "merge" stage of `crate::linker` for this, or even
336    // delegate to `run_fat_lto` (although `-Zcombine-cgu` is much more niche).
337    fn run_link(
338        cgcx: &CodegenContext<Self>,
339        diag_handler: DiagCtxtHandle<'_>,
340        _modules: Vec<ModuleCodegen<Self::Module>>,
341    ) -> Result<ModuleCodegen<Self::Module>, FatalError> {
342        assert!(
343            cgcx.opts.unstable_opts.combine_cgu,
344            "`run_link` (for `WorkItemResult::NeedsLink`) should \
345             only be invoked due to `-Zcombine-cgu`"
346        );
347        diag_handler.fatal("Rust-GPU does not support `-Zcombine-cgu`")
348    }
349
350    // FIXME(eddyb) reuse the "merge" stage of `crate::linker` for this, or even
351    // consider setting `requires_lto = true` in the target specs and moving the
352    // entirety of `crate::linker` into this stage (lacking diagnostics may be
353    // an issue - it's surprising `CodegenBackend::link` has `Session` at all).
354    fn run_fat_lto(
355        cgcx: &CodegenContext<Self>,
356        _modules: Vec<FatLtoInput<Self>>,
357        _cached_modules: Vec<(SerializedModule<Self::ModuleBuffer>, WorkProduct)>,
358    ) -> Result<LtoModuleCodegen<Self>, FatalError> {
359        assert!(
360            cgcx.lto == rustc_session::config::Lto::Fat,
361            "`run_fat_lto` (for `WorkItemResult::NeedsFatLto`) should \
362             only be invoked due to `-Clto` (or equivalent)"
363        );
364        unreachable!("Rust-GPU does not support fat LTO")
365    }
366
367    fn run_thin_lto(
368        cgcx: &CodegenContext<Self>,
369        modules: Vec<(String, Self::ThinBuffer)>,
370        cached_modules: Vec<(SerializedModule<Self::ModuleBuffer>, WorkProduct)>,
371    ) -> Result<(Vec<LtoModuleCodegen<Self>>, Vec<WorkProduct>), FatalError> {
372        link::run_thin(cgcx, modules, cached_modules)
373    }
374
375    fn print_pass_timings(&self) {
376        warn!("TODO: Implement print_pass_timings");
377    }
378
379    fn print_statistics(&self) {
380        warn!("TODO: Implement print_statistics");
381    }
382
383    fn optimize(
384        cgcx: &CodegenContext<Self>,
385        _dcx: DiagCtxtHandle<'_>,
386        module: &mut ModuleCodegen<Self::Module>,
387        _config: &ModuleConfig,
388    ) -> Result<(), FatalError> {
389        Self::optimize_common(cgcx, module)
390    }
391
392    fn optimize_thin(
393        cgcx: &CodegenContext<Self>,
394        thin_module: ThinModule<Self>,
395    ) -> Result<ModuleCodegen<Self::Module>, FatalError> {
396        // FIXME(eddyb) the inefficiency of Module -> [u8] -> Module roundtrips
397        // comes from upstream and it applies to `rustc_codegen_llvm` as well,
398        // eventually it should be properly addressed (for `ThinLocal` at least).
399        let mut module = ModuleCodegen {
400            module_llvm: link::with_rspirv_loader(|loader| {
401                rspirv::binary::parse_bytes(thin_module.data(), loader)
402            })
403            .unwrap(),
404            name: thin_module.name().to_string(),
405            kind: ModuleKind::Regular,
406            thin_lto_buffer: None,
407        };
408        Self::optimize_common(cgcx, &mut module)?;
409        Ok(module)
410    }
411
412    fn optimize_fat(
413        cgcx: &CodegenContext<Self>,
414        module: &mut ModuleCodegen<Self::Module>,
415    ) -> Result<(), FatalError> {
416        Self::optimize_common(cgcx, module)
417    }
418
419    fn codegen(
420        cgcx: &CodegenContext<Self>,
421        _diag_handler: DiagCtxtHandle<'_>,
422        module: ModuleCodegen<Self::Module>,
423        _config: &ModuleConfig,
424    ) -> Result<CompiledModule, FatalError> {
425        let kind = module.kind;
426        let (name, module_buffer) = Self::serialize_module(module);
427
428        let path = cgcx.output_filenames.temp_path_for_cgu(
429            OutputType::Object,
430            &name,
431            cgcx.invocation_temp.as_deref(),
432        );
433        fs::write(&path, module_buffer.as_bytes()).unwrap();
434
435        Ok(CompiledModule {
436            name,
437            kind,
438            object: Some(path),
439            dwarf_object: None,
440            bytecode: None,
441            assembly: None,
442            llvm_ir: None,
443            links_from_incr_cache: vec![],
444        })
445    }
446
447    fn prepare_thin(
448        module: ModuleCodegen<Self::Module>,
449        _want_summary: bool,
450    ) -> (String, Self::ThinBuffer) {
451        Self::serialize_module(module)
452    }
453
454    fn serialize_module(module: ModuleCodegen<Self::Module>) -> (String, Self::ModuleBuffer) {
455        (
456            module.name,
457            SpirvModuleBuffer(module.module_llvm.assemble()),
458        )
459    }
460
461    fn autodiff(
462        _cgcx: &CodegenContext<Self>,
463        _module: &ModuleCodegen<Self::Module>,
464        _diff_fncs: Vec<AutoDiffItem>,
465        _config: &ModuleConfig,
466    ) -> Result<(), FatalError> {
467        unreachable!("Rust-GPU does not support autodiff")
468    }
469}
470
471impl ExtraBackendMethods for SpirvCodegenBackend {
472    fn codegen_allocator(
473        &self,
474        _: TyCtxt<'_>,
475        _: &str,
476        _: AllocatorKind,
477        _: AllocatorKind,
478    ) -> Self::Module {
479        todo!()
480    }
481
482    fn compile_codegen_unit<'tcx>(
483        &self,
484        tcx: TyCtxt<'tcx>,
485        cgu_name: Symbol,
486    ) -> (ModuleCodegen<Self::Module>, u64) {
487        let _timer = tcx
488            .prof
489            .verbose_generic_activity_with_arg("codegen_module", cgu_name.to_string());
490
491        // TODO: Do dep_graph stuff
492        let cgu = tcx.codegen_unit(cgu_name);
493
494        let mut cx = CodegenCx::new(tcx, cgu);
495        let do_codegen = |cx: &mut CodegenCx<'tcx>| {
496            let mono_items = cgu.items_in_deterministic_order(cx.tcx);
497
498            if let Some(dir) = &cx.codegen_args.dump_mir {
499                dump_mir(tcx, mono_items.as_slice(), &dir.join(cgu_name.to_string()));
500            }
501
502            for &(mono_item, mono_item_data) in mono_items.iter() {
503                mono_item.predefine::<Builder<'_, '_>>(
504                    cx,
505                    cgu_name.as_str(),
506                    mono_item_data.linkage,
507                    mono_item_data.visibility,
508                );
509            }
510
511            // ... and now that we have everything pre-defined, fill out those definitions.
512            for &(mono_item, mono_item_data) in &mono_items {
513                mono_item.define::<Builder<'_, '_>>(cx, cgu_name.as_str(), mono_item_data);
514            }
515
516            if let Some(_entry) = maybe_create_entry_wrapper::<Builder<'_, '_>>(cx, cgu) {
517                // attributes::sanitize(&cx, SanitizerSet::empty(), entry);
518            }
519        };
520        // HACK(eddyb) mutable access needed for `mono_item.define::<...>(cx, ...)`
521        // but that alone leads to needless cloning and smuggling a mutable borrow
522        // through `DumpModuleOnPanic` (for both its `Drop` impl and `do_codegen`).
523        if let Some(path) = cx.codegen_args.dump_module_on_panic.clone() {
524            let module_dumper = DumpModuleOnPanic {
525                cx: &mut cx,
526                path: &path,
527            };
528            with_no_trimmed_paths!(do_codegen(module_dumper.cx));
529            drop(module_dumper);
530        } else {
531            with_no_trimmed_paths!(do_codegen(&mut cx));
532        }
533
534        (
535            ModuleCodegen {
536                name: cgu_name.to_string(),
537                module_llvm: cx.finalize_module(),
538                kind: ModuleKind::Regular,
539                thin_lto_buffer: None,
540            },
541            0,
542        )
543    }
544
545    fn target_machine_factory(
546        &self,
547        _sess: &Session,
548        _opt_level: config::OptLevel,
549        _target_features: &[String],
550    ) -> Arc<(dyn Fn(TargetMachineFactoryConfig) -> Result<(), String> + Send + Sync + 'static)>
551    {
552        Arc::new(|_| Ok(()))
553    }
554}
555
556struct DumpModuleOnPanic<'a, 'cx, 'tcx> {
557    cx: &'cx mut CodegenCx<'tcx>,
558    path: &'a Path,
559}
560
561impl Drop for DumpModuleOnPanic<'_, '_, '_> {
562    fn drop(&mut self) {
563        if std::thread::panicking() {
564            if self.path.has_root() {
565                self.cx.builder.dump_module(self.path);
566            } else {
567                error!("{}", self.cx.builder.dump_module_str());
568            }
569        }
570    }
571}
572
573/// This is the entrypoint for a hot plugged `rustc_codegen_spirv`
574#[unsafe(no_mangle)]
575pub fn __rustc_codegen_backend() -> Box<dyn CodegenBackend> {
576    // Tweak rustc's default ICE panic hook, to direct people to `rust-gpu`.
577    rustc_driver::install_ice_hook("https://github.com/rust-gpu/rust-gpu/issues/new", |dcx| {
578        dcx.handle().note(concat!(
579            "`rust-gpu` version `",
580            env!("CARGO_PKG_VERSION"),
581            "`"
582        ));
583    });
584
585    Box::new(SpirvCodegenBackend)
586}
587
588// Set up logging/tracing. See https://github.com/Rust-GPU/rust-gpu/issues/192.
589fn init_logging(sess: &Session) {
590    use std::env::{self, VarError};
591    use std::io::{self, IsTerminal};
592    use tracing_subscriber::layer::SubscriberExt;
593
594    // Set up the default subscriber with optional filtering.
595    let filter = tracing_subscriber::EnvFilter::from_env("RUSTGPU_LOG");
596    #[cfg(not(rustc_codegen_spirv_disable_pqp_cg_ssa))]
597    let filter = filter.add_directive("rustc_codegen_spirv::maybe_pqp_cg_ssa=off".parse().unwrap());
598    let subscriber = tracing_subscriber::Registry::default().with(filter);
599
600    #[derive(Debug, Default)]
601    enum OutputFormat {
602        #[default]
603        Tree,
604        Flat,
605        Json,
606    }
607
608    let output_format = match env::var("RUSTGPU_LOG_FORMAT").as_deref() {
609        Ok("tree") | Err(VarError::NotPresent) => OutputFormat::Tree,
610        Ok("flat") => OutputFormat::Flat,
611        Ok("json") => OutputFormat::Json,
612        Ok(value) => sess.dcx().fatal(format!(
613            "invalid output format value '{value}': expected one of tree, flat, or json",
614        )),
615        Err(VarError::NotUnicode(value)) => sess.dcx().fatal(format!(
616            "invalid output format value '{}': expected one of tree, flat, or json",
617            value.to_string_lossy()
618        )),
619    };
620
621    let subscriber: Box<dyn tracing::Subscriber + Send + Sync> = match output_format {
622        OutputFormat::Tree => {
623            // TODO(@LegNeato): Query dcx color support when rustc exposes it.
624            let color_logs = match env::var("RUSTGPU_LOG_COLOR").as_deref() {
625                Ok("always") => true,
626                Ok("never") => false,
627                Ok("auto") | Err(VarError::NotPresent) => io::stderr().is_terminal(),
628                Ok(value) => sess.dcx().fatal(format!(
629                    "invalid log color value '{value}': expected one of always, never, or auto",
630                )),
631                Err(VarError::NotUnicode(value)) => sess.dcx().fatal(format!(
632                    "invalid log color value '{}': expected one of always, never, or auto",
633                    value.to_string_lossy()
634                )),
635            };
636
637            let tree_layer = tracing_tree::HierarchicalLayer::default()
638                .with_writer(io::stderr)
639                .with_ansi(color_logs)
640                .with_targets(true)
641                .with_wraparound(10)
642                .with_verbose_exit(true)
643                .with_verbose_entry(true)
644                .with_indent_amount(2);
645
646            #[cfg(debug_assertions)]
647            let tree_layer = tree_layer.with_thread_ids(true).with_thread_names(true);
648
649            Box::new(subscriber.with(tree_layer))
650        }
651        OutputFormat::Flat => Box::new(subscriber),
652        OutputFormat::Json => Box::new(subscriber.with(tracing_subscriber::fmt::layer().json())),
653    };
654    tracing::subscriber::set_global_default(subscriber).unwrap();
655}