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}