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