rustc_codegen_spirv/
custom_insts.rs

1//! SPIR-V (extended) instructions specific to `rustc_codegen_spirv`, produced
2//! during the original codegen of a crate, and consumed by the `linker`.
3
4use lazy_static::lazy_static;
5use rspirv::dr::{Instruction, Operand};
6use rspirv::spirv::Op;
7use smallvec::SmallVec;
8
9/// Prefix for `CUSTOM_EXT_INST_SET` (`OpExtInstImport` "instruction set" name),
10/// without any of the disambiguating suffixes added for specific revisions.
11///
12/// This **should not** be changed (if possible), to ensure version mismatches
13/// can be detected (i.e. starting with this prefix, but the full name differs).
14///
15/// See `CUSTOM_EXT_INST_SET`'s docs for further constraints on the full name.
16pub const CUSTOM_EXT_INST_SET_PREFIX: &str = concat!("Rust.", env!("CARGO_PKG_NAME"), ".");
17
18macro_rules! join_cargo_pkg_version_major_minor_patch {
19    ($sep:literal) => {
20        concat!(
21            env!("CARGO_PKG_VERSION_MAJOR"),
22            $sep,
23            env!("CARGO_PKG_VERSION_MINOR"),
24            $sep,
25            env!("CARGO_PKG_VERSION_PATCH"),
26        )
27    };
28}
29
30lazy_static! {
31    /// `OpExtInstImport` "instruction set" name for all Rust-GPU instructions.
32    ///
33    /// These considerations are relevant to the specific choice of name:
34    /// * does *not* start with `NonSemantic.`, as:
35    ///   * some custom instructions may need to be semantic
36    ///   * these custom instructions are not meant for the final SPIR-V
37    ///     (so no third-party support is *technically* required for them)
38    ///   * `NonSemantic.` requires SPIR-V 1.6 (or `SPV_KHR_non_semantic_info`)
39    /// * always starts with `CUSTOM_EXT_INST_SET_PREFIX` (see also its docs),
40    ///   regardless of Rust-GPU version or custom instruction set definition
41    /// * contains enough disambiguating information to avoid misinterpretation
42    ///   if the definitions of the custom instructions have changed - this is
43    ///   achieved by hashing the `SCHEMA` constant from `def_custom_insts!` below
44    pub static ref CUSTOM_EXT_INST_SET: String = {
45        let schema_hash = {
46            use rustc_data_structures::stable_hasher::StableHasher;
47            use rustc_hashes::Hash128;
48            use std::hash::Hash;
49
50            let mut hasher = StableHasher::new();
51            SCHEMA.hash(&mut hasher);
52            hasher.finish::<Hash128>().as_u128()
53        };
54        let version = join_cargo_pkg_version_major_minor_patch!("_");
55        format!("{CUSTOM_EXT_INST_SET_PREFIX}{version}.{schema_hash:x}")
56    };
57}
58
59pub fn register_to_spirt_context(cx: &spirt::Context) {
60    use spirt::spv::spec::{ExtInstSetDesc, ExtInstSetInstructionDesc};
61    cx.register_custom_ext_inst_set(
62        &CUSTOM_EXT_INST_SET,
63        ExtInstSetDesc {
64            // HACK(eddyb) this is the most compact form I've found, that isn't
65            // outright lossy by omitting "Rust vs Rust-GPU" or the version.
66            short_alias: Some(
67                concat!("Rust-GPU ", join_cargo_pkg_version_major_minor_patch!(".")).into(),
68            ),
69            instructions: SCHEMA
70                .iter()
71                .map(|&(i, name, operand_names)| {
72                    (
73                        i,
74                        ExtInstSetInstructionDesc {
75                            name: name.into(),
76                            operand_names: operand_names
77                                .iter()
78                                .map(|name| {
79                                    name.strip_prefix("..")
80                                        .unwrap_or(name)
81                                        .replace('_', " ")
82                                        .into()
83                                })
84                                .collect(),
85                            is_debuginfo: name.contains("Debug")
86                                || name.contains("InlinedCallFrame"),
87                        },
88                    )
89                })
90                .collect(),
91        },
92    );
93}
94
95macro_rules! def_custom_insts {
96    ($($num:literal => $name:ident $({ $($field:ident),+ $(, ..$variadic_field:ident)? $(,)? })?),+ $(,)?) => {
97        const SCHEMA: &[(u32, &str, &[&str])] = &[
98            $(($num, stringify!($name), &[$($(stringify!($field),)+ $(stringify!(..$variadic_field),)?)?])),+
99        ];
100
101        #[repr(u32)]
102        #[derive(Copy, Clone, Debug, PartialEq, Eq)]
103        pub enum CustomOp { $($name = $num),+ }
104
105        impl CustomOp {
106            pub fn decode(i: u32) -> Self {
107                match i {
108                    $($num => Self::$name,)+
109                    _ => unreachable!("{i} is not a valid custom instruction number"),
110                }
111            }
112
113            pub fn decode_from_ext_inst(inst: &Instruction) -> Self {
114                assert_eq!(inst.class.opcode, Op::ExtInst);
115                Self::decode(inst.operands[1].unwrap_literal_ext_inst_integer())
116            }
117
118            pub fn with_operands<T: Clone>(self, operands: &[T]) -> CustomInst<T> {
119                match self {
120                    $(Self::$name => match operands {
121                        [$($($field,)+ $($variadic_field @ ..)?)?] => CustomInst::$name $({
122                            $($field: $field.clone(),)+
123                            $($variadic_field: $variadic_field.iter().cloned().collect())?
124                        })?,
125                        _ => unreachable!("{self:?} does not have the right number of operands"),
126                    }),+
127                }
128            }
129        }
130
131        #[derive(Clone, Debug)]
132        pub enum CustomInst<T> {
133            $($name $({ $($field: T,)+ $($variadic_field: SmallVec<[T; 4]>)? })?),+
134        }
135
136        impl<T> CustomInst<T> {
137            pub fn op(&self) -> CustomOp {
138                match *self {
139                    $(Self::$name { .. } => CustomOp::$name),+
140                }
141            }
142
143            // HACK(eddyb) this should return an iterator, but that's too much effort.
144            pub fn into_operands(self) -> SmallVec<[T; 8]> {
145                match self {
146                    $(Self::$name $({ $($field,)+ $($variadic_field)? })? => {
147                        [$($($field),+)?].into_iter() $($(.chain($variadic_field))?)? .collect()
148                    })+
149                }
150            }
151        }
152
153        impl CustomInst<Operand> {
154            pub fn decode(inst: &Instruction) -> Self {
155                CustomOp::decode_from_ext_inst(inst).with_operands(&inst.operands[2..])
156            }
157        }
158    }
159}
160
161// NOTE(eddyb) several of these are similar to `NonSemantic.Shader.DebugInfo.100`
162// instructions, but simpler (to aid implementation, for now).
163def_custom_insts! {
164    // Like `DebugLine` (from `NonSemantic.Shader.DebugInfo.100`) or `OpLine`.
165    0 => SetDebugSrcLoc { file, line_start, line_end, col_start, col_end },
166    // Like `DebugNoLine` (from `NonSemantic.Shader.DebugInfo.100`) or `OpNoLine`.
167    1 => ClearDebugSrcLoc,
168
169    // Similar to `DebugInlinedAt` (from `NonSemantic.Shader.DebugInfo.100`),
170    // but simpler: there are no "scope objects", the location of the inlined
171    // callsite is given by other debuginfo (`SetDebugSrcLoc`/`OpLine`) active
172    // before this instruction, and only the name of the callee is recorded.
173    2 => PushInlinedCallFrame { callee_name },
174    // Leave the most recent inlined call frame entered by a `PushInlinedCallFrame`
175    // (i.e. the inlined call frames form a virtual call stack in debuginfo).
176    3 => PopInlinedCallFrame,
177
178    // [Semantic] Similar to some proposed `OpAbort`, but without any ability to
179    // indicate abnormal termination (so it's closer to `OpTerminateInvocation`,
180    // which we could theoretically use, but that's limited to fragment shaders).
181    //
182    // Lowering takes advantage of inlining happening before CFG structurization
183    // (by forcing inlining of `Abort`s all the way up to entry-points, as to be
184    // able to turn the `Abort`s into regular `OpReturn`s, from an entry-point),
185    // but if/when inlining works on structured SPIR-T instead, it's not much
186    // harder to make any call to a "may (transitively) abort" function branch on
187    // an additional returned `bool`, instead (i.e. a form of emulated unwinding).
188    //
189    // As this is a custom terminator, it must only appear before `OpUnreachable`,
190    // with at most debuginfo instructions (standard or custom), between the two.
191    //
192    // FIXME(eddyb) long-term this kind of custom control-flow could be generalized
193    // to fully emulate unwinding (resulting in codegen similar to `?` in functions
194    // returning `Option` or `Result`), to e.g. run destructors, or even allow
195    // users to do `catch_unwind` at the top-level of their shader to handle
196    // panics specially (e.g. by appending to a custom buffer, or using some
197    // specific color in a fragment shader, to indicate a panic happened).
198    // NOTE(eddyb) `message_debug_printf` operands form a complete `debugPrintf`
199    // invocation (format string followed by inputs) for the "message", while
200    // `kind` only distinguishes broad categories like `"abort"` vs `"panic"`.
201    4 => Abort { kind, ..message_debug_printf },
202}
203
204impl CustomOp {
205    /// Returns `true` iff this `CustomOp` is a custom debuginfo instruction,
206    /// i.e. non-semantic (can/must be ignored wherever `OpLine`/`OpNoLine` are).
207    pub fn is_debuginfo(self) -> bool {
208        match self {
209            CustomOp::SetDebugSrcLoc
210            | CustomOp::ClearDebugSrcLoc
211            | CustomOp::PushInlinedCallFrame
212            | CustomOp::PopInlinedCallFrame => true,
213
214            CustomOp::Abort => false,
215        }
216    }
217
218    /// Returns `true` iff this `CustomOp` is a custom terminator instruction,
219    /// i.e. semantic and must precede an `OpUnreachable` standard terminator,
220    /// with at most debuginfo instructions (standard or custom), between the two.
221    pub fn is_terminator(self) -> bool {
222        match self {
223            CustomOp::SetDebugSrcLoc
224            | CustomOp::ClearDebugSrcLoc
225            | CustomOp::PushInlinedCallFrame
226            | CustomOp::PopInlinedCallFrame => false,
227
228            CustomOp::Abort => true,
229        }
230    }
231}