mirror of
https://github.com/EmbarkStudios/rust-gpu.git
synced 2024-11-25 00:04:11 +00:00
Add custom_insts
for our own OpExtInst
s, and use it for some debuginfo.
This commit is contained in:
parent
69349b1b9d
commit
c5dcd035b1
@ -1,6 +1,7 @@
|
|||||||
use super::Builder;
|
use super::Builder;
|
||||||
use crate::abi::ConvSpirvType;
|
use crate::abi::ConvSpirvType;
|
||||||
use crate::builder_spirv::{BuilderCursor, SpirvConst, SpirvValue, SpirvValueExt, SpirvValueKind};
|
use crate::builder_spirv::{BuilderCursor, SpirvConst, SpirvValue, SpirvValueExt, SpirvValueKind};
|
||||||
|
use crate::custom_insts::{CustomInst, CustomOp};
|
||||||
use crate::rustc_codegen_ssa::traits::BaseTypeMethods;
|
use crate::rustc_codegen_ssa::traits::BaseTypeMethods;
|
||||||
use crate::spirv_type::SpirvType;
|
use crate::spirv_type::SpirvType;
|
||||||
use rspirv::dr::{InsertPoint, Instruction, Operand};
|
use rspirv::dr::{InsertPoint, Instruction, Operand};
|
||||||
@ -656,15 +657,48 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_span(&mut self, span: Span) {
|
fn set_span(&mut self, span: Span) {
|
||||||
self.current_span = Some(span);
|
let old_span = self.current_span.replace(span);
|
||||||
|
|
||||||
// We may not always have valid spans.
|
// FIXME(eddyb) enable this once cross-block interactions are figured out
|
||||||
// FIXME(eddyb) reduce the sources of this as much as possible.
|
// (in particular, every block starts off with no debuginfo active).
|
||||||
if span.is_dummy() {
|
if false {
|
||||||
self.emit().no_line();
|
// Avoid redundant debuginfo.
|
||||||
|
if old_span == Some(span) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HACK(eddyb) this is only to aid testing (and to not remove the old code).
|
||||||
|
let use_custom_insts = true;
|
||||||
|
|
||||||
|
if use_custom_insts {
|
||||||
|
// FIXME(eddyb) this should be cached more efficiently.
|
||||||
|
let void_ty = SpirvType::Void.def(rustc_span::DUMMY_SP, self);
|
||||||
|
|
||||||
|
// We may not always have valid spans.
|
||||||
|
// FIXME(eddyb) reduce the sources of this as much as possible.
|
||||||
|
if span.is_dummy() {
|
||||||
|
self.custom_inst(void_ty, CustomInst::ClearDebugSrcLoc);
|
||||||
|
} else {
|
||||||
|
let (file, line, col) = self.builder.file_line_col_for_op_line(span);
|
||||||
|
self.custom_inst(
|
||||||
|
void_ty,
|
||||||
|
CustomInst::SetDebugSrcLoc {
|
||||||
|
file: Operand::IdRef(file.file_name_op_string_id),
|
||||||
|
line: Operand::IdRef(self.const_u32(line).def(self)),
|
||||||
|
col: Operand::IdRef(self.const_u32(col).def(self)),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let (file, line, col) = self.builder.file_line_col_for_op_line(span);
|
// We may not always have valid spans.
|
||||||
self.emit().line(file.file_name_op_string_id, line, col);
|
// FIXME(eddyb) reduce the sources of this as much as possible.
|
||||||
|
if span.is_dummy() {
|
||||||
|
self.emit().no_line();
|
||||||
|
} else {
|
||||||
|
let (file, line, col) = self.builder.file_line_col_for_op_line(span);
|
||||||
|
self.emit().line(file.file_name_op_string_id, line, col);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2359,6 +2393,8 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
|
|||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let custom_ext_inst_set_import = self.ext_inst.borrow_mut().import_custom(self);
|
||||||
|
|
||||||
// HACK(eddyb) we can remove SSA instructions even when they have
|
// HACK(eddyb) we can remove SSA instructions even when they have
|
||||||
// side-effects, *as long as* they are "local" enough and cannot
|
// side-effects, *as long as* they are "local" enough and cannot
|
||||||
// be observed from outside this current invocation - because the
|
// be observed from outside this current invocation - because the
|
||||||
@ -2372,7 +2408,14 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
|
|||||||
.instructions
|
.instructions
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(_, inst)| ![Op::Line, Op::NoLine].contains(&inst.class.opcode));
|
.filter(|(_, inst)| {
|
||||||
|
let is_standard_debug = [Op::Line, Op::NoLine].contains(&inst.class.opcode);
|
||||||
|
let is_custom_debug = inst.class.opcode == Op::ExtInst
|
||||||
|
&& inst.operands[0].unwrap_id_ref() == custom_ext_inst_set_import
|
||||||
|
&& [CustomOp::SetDebugSrcLoc, CustomOp::ClearDebugSrcLoc]
|
||||||
|
.contains(&CustomOp::decode_from_ext_inst(inst));
|
||||||
|
!(is_standard_debug || is_custom_debug)
|
||||||
|
});
|
||||||
let mut relevant_insts_next_back = |expected_op| {
|
let mut relevant_insts_next_back = |expected_op| {
|
||||||
non_debug_insts
|
non_debug_insts
|
||||||
.next_back()
|
.next_back()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use super::Builder;
|
use super::Builder;
|
||||||
use crate::builder_spirv::{SpirvValue, SpirvValueExt};
|
use crate::builder_spirv::{SpirvValue, SpirvValueExt};
|
||||||
|
use crate::custom_insts;
|
||||||
use rspirv::spirv::{GLOp, Word};
|
use rspirv::spirv::{GLOp, Word};
|
||||||
use rspirv::{dr::Operand, spirv::Capability};
|
use rspirv::{dr::Operand, spirv::Capability};
|
||||||
|
|
||||||
@ -8,11 +9,26 @@ const GLSL_STD_450: &str = "GLSL.std.450";
|
|||||||
/// Manager for OpExtInst/OpExtImport instructions
|
/// Manager for OpExtInst/OpExtImport instructions
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ExtInst {
|
pub struct ExtInst {
|
||||||
|
/// See `crate::custom_insts` for more details on what this entails.
|
||||||
|
custom: Option<Word>,
|
||||||
|
|
||||||
glsl: Option<Word>,
|
glsl: Option<Word>,
|
||||||
integer_functions_2_intel: bool,
|
integer_functions_2_intel: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtInst {
|
impl ExtInst {
|
||||||
|
pub fn import_custom(&mut self, bx: &Builder<'_, '_>) -> Word {
|
||||||
|
if let Some(id) = self.custom {
|
||||||
|
id
|
||||||
|
} else {
|
||||||
|
let id = bx
|
||||||
|
.emit_global()
|
||||||
|
.ext_inst_import(custom_insts::CUSTOM_EXT_INST_SET.clone());
|
||||||
|
self.custom = Some(id);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn import_glsl(&mut self, bx: &Builder<'_, '_>) -> Word {
|
pub fn import_glsl(&mut self, bx: &Builder<'_, '_>) -> Word {
|
||||||
if let Some(id) = self.glsl {
|
if let Some(id) = self.glsl {
|
||||||
id
|
id
|
||||||
@ -46,6 +62,24 @@ impl ExtInst {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tcx> Builder<'a, 'tcx> {
|
impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
|
pub fn custom_inst(
|
||||||
|
&mut self,
|
||||||
|
result_type: Word,
|
||||||
|
inst: custom_insts::CustomInst<Operand>,
|
||||||
|
) -> SpirvValue {
|
||||||
|
let custom_ext_inst_set = self.ext_inst.borrow_mut().import_custom(self);
|
||||||
|
self.emit()
|
||||||
|
.ext_inst(
|
||||||
|
result_type,
|
||||||
|
None,
|
||||||
|
custom_ext_inst_set,
|
||||||
|
inst.op() as u32,
|
||||||
|
inst.into_operands(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.with_type(result_type)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn gl_op(
|
pub fn gl_op(
|
||||||
&mut self,
|
&mut self,
|
||||||
op: GLOp,
|
op: GLOp,
|
||||||
|
@ -369,6 +369,11 @@ impl CodegenArgs {
|
|||||||
"dump the SPIR-T module across passes, to a (pair of) file(s) in DIR",
|
"dump the SPIR-T module across passes, to a (pair of) file(s) in DIR",
|
||||||
"DIR",
|
"DIR",
|
||||||
);
|
);
|
||||||
|
opts.optflag(
|
||||||
|
"",
|
||||||
|
"spirt-keep-custom-debuginfo-in-dumps",
|
||||||
|
"keep custom debuginfo when dumping SPIR-T (instead of lossily prettifying it)",
|
||||||
|
);
|
||||||
opts.optflag(
|
opts.optflag(
|
||||||
"",
|
"",
|
||||||
"specializer-debug",
|
"specializer-debug",
|
||||||
@ -534,6 +539,8 @@ impl CodegenArgs {
|
|||||||
dump_post_merge: matches_opt_dump_dir_path("dump-post-merge"),
|
dump_post_merge: matches_opt_dump_dir_path("dump-post-merge"),
|
||||||
dump_post_split: matches_opt_dump_dir_path("dump-post-split"),
|
dump_post_split: matches_opt_dump_dir_path("dump-post-split"),
|
||||||
dump_spirt_passes: matches_opt_dump_dir_path("dump-spirt-passes"),
|
dump_spirt_passes: matches_opt_dump_dir_path("dump-spirt-passes"),
|
||||||
|
spirt_keep_custom_debuginfo_in_dumps: matches
|
||||||
|
.opt_present("spirt-keep-custom-debuginfo-in-dumps"),
|
||||||
specializer_debug: matches.opt_present("specializer-debug"),
|
specializer_debug: matches.opt_present("specializer-debug"),
|
||||||
specializer_dump_instances: matches_opt_path("specializer-dump-instances"),
|
specializer_dump_instances: matches_opt_path("specializer-dump-instances"),
|
||||||
print_all_zombie: matches.opt_present("print-all-zombie"),
|
print_all_zombie: matches.opt_present("print-all-zombie"),
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
//! the original codegen of a crate, and consumed by the `linker`.
|
//! the original codegen of a crate, and consumed by the `linker`.
|
||||||
|
|
||||||
use crate::builder_spirv::BuilderSpirv;
|
use crate::builder_spirv::BuilderSpirv;
|
||||||
|
use crate::custom_insts::{self, CustomInst};
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rspirv::dr::{Instruction, Module, Operand};
|
use rspirv::dr::{Instruction, Module, Operand};
|
||||||
@ -201,6 +202,14 @@ pub struct SpanRegenerator<'a> {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct SpvDebugInfo<'a> {
|
struct SpvDebugInfo<'a> {
|
||||||
|
/// ID of `OpExtInstImport` for our custom "extended instruction set",
|
||||||
|
/// if present (see `crate::custom_insts` for more details).
|
||||||
|
custom_ext_inst_set_import: Option<Word>,
|
||||||
|
|
||||||
|
// HACK(eddyb) this is only needed because `OpExtInst`s can't have immediates,
|
||||||
|
// and must resort to referencing `OpConstant`s instead.
|
||||||
|
id_to_op_constant_operand: FxIndexMap<Word, &'a Operand>,
|
||||||
|
|
||||||
id_to_op_string: FxIndexMap<Word, &'a str>,
|
id_to_op_string: FxIndexMap<Word, &'a str>,
|
||||||
files: FxIndexMap<&'a str, SpvDebugFile<'a>>,
|
files: FxIndexMap<&'a str, SpvDebugFile<'a>>,
|
||||||
}
|
}
|
||||||
@ -235,6 +244,24 @@ impl<'a> SpvDebugInfo<'a> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FIXME(eddyb) avoid repeating this across different passes/helpers.
|
||||||
|
this.custom_ext_inst_set_import = module
|
||||||
|
.ext_inst_imports
|
||||||
|
.iter()
|
||||||
|
.find(|inst| {
|
||||||
|
assert_eq!(inst.class.opcode, Op::ExtInstImport);
|
||||||
|
inst.operands[0].unwrap_literal_string() == &custom_insts::CUSTOM_EXT_INST_SET[..]
|
||||||
|
})
|
||||||
|
.map(|inst| inst.result_id.unwrap());
|
||||||
|
|
||||||
|
this.id_to_op_constant_operand.extend(
|
||||||
|
module
|
||||||
|
.types_global_values
|
||||||
|
.iter()
|
||||||
|
.filter(|inst| inst.class.opcode == Op::Constant)
|
||||||
|
.map(|inst| (inst.result_id.unwrap(), &inst.operands[0])),
|
||||||
|
);
|
||||||
|
|
||||||
let mut insts = module.debug_string_source.iter().peekable();
|
let mut insts = module.debug_string_source.iter().peekable();
|
||||||
while let Some(inst) = insts.next() {
|
while let Some(inst) = insts.next() {
|
||||||
match inst.class.opcode {
|
match inst.class.opcode {
|
||||||
@ -327,20 +354,40 @@ impl<'a> SpanRegenerator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Extract the equivalent `SrcLocDecoration` from a debug instruction that
|
/// Extract the equivalent `SrcLocDecoration` from a debug instruction that
|
||||||
/// specifies some source location (currently only `OpLine` is supported).
|
/// specifies some source location (both the standard `OpLine`, and our own
|
||||||
|
/// custom instruction, i.e. `CustomInst::SetDebugSrcLoc`, are supported).
|
||||||
pub fn src_loc_from_debug_inst(&mut self, inst: &Instruction) -> Option<SrcLocDecoration<'a>> {
|
pub fn src_loc_from_debug_inst(&mut self, inst: &Instruction) -> Option<SrcLocDecoration<'a>> {
|
||||||
assert_eq!(inst.class.opcode, Op::Line);
|
let spv_debug_info = self
|
||||||
|
.spv_debug_info
|
||||||
|
.get_or_insert_with(|| SpvDebugInfo::collect(self.module));
|
||||||
|
|
||||||
let (file_id, line, col) = match inst.class.opcode {
|
let (file_id, line, col) = match inst.class.opcode {
|
||||||
Op::Line => (
|
Op::Line => (
|
||||||
inst.operands[0].unwrap_id_ref(),
|
inst.operands[0].unwrap_id_ref(),
|
||||||
inst.operands[1].unwrap_literal_int32(),
|
inst.operands[1].unwrap_literal_int32(),
|
||||||
inst.operands[2].unwrap_literal_int32(),
|
inst.operands[2].unwrap_literal_int32(),
|
||||||
),
|
),
|
||||||
|
Op::ExtInst
|
||||||
|
if Some(inst.operands[0].unwrap_id_ref())
|
||||||
|
== spv_debug_info.custom_ext_inst_set_import =>
|
||||||
|
{
|
||||||
|
match CustomInst::decode(inst) {
|
||||||
|
CustomInst::SetDebugSrcLoc { file, line, col } => (
|
||||||
|
file.unwrap_id_ref(),
|
||||||
|
spv_debug_info.id_to_op_constant_operand[&line.unwrap_id_ref()]
|
||||||
|
.unwrap_literal_int32(),
|
||||||
|
spv_debug_info.id_to_op_constant_operand[&col.unwrap_id_ref()]
|
||||||
|
.unwrap_literal_int32(),
|
||||||
|
),
|
||||||
|
custom_inst @ CustomInst::ClearDebugSrcLoc => {
|
||||||
|
unreachable!("src_loc_from_debug_inst({inst:?} => {custom_inst:?})")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => unreachable!("src_loc_from_debug_inst({inst:?})"),
|
_ => unreachable!("src_loc_from_debug_inst({inst:?})"),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.spv_debug_info
|
spv_debug_info
|
||||||
.get_or_insert_with(|| SpvDebugInfo::collect(self.module))
|
|
||||||
.id_to_op_string
|
.id_to_op_string
|
||||||
.get(&file_id)
|
.get(&file_id)
|
||||||
.map(|&file_name| SrcLocDecoration {
|
.map(|&file_name| SrcLocDecoration {
|
||||||
|
118
crates/rustc_codegen_spirv/src/custom_insts.rs
Normal file
118
crates/rustc_codegen_spirv/src/custom_insts.rs
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
//! 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"), ".");
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
const VER_MAJOR: &str = env!("CARGO_PKG_VERSION_MAJOR");
|
||||||
|
const VER_MINOR: &str = env!("CARGO_PKG_VERSION_MINOR");
|
||||||
|
const VER_PATCH: &str = env!("CARGO_PKG_VERSION_PATCH");
|
||||||
|
|
||||||
|
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)
|
||||||
|
};
|
||||||
|
|
||||||
|
format!("{CUSTOM_EXT_INST_SET_PREFIX}{VER_MAJOR}_{VER_MINOR}_{VER_PATCH}.{schema_hash:x}")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! def_custom_insts {
|
||||||
|
($($num:literal => $name:ident $({ $($field:ident),+ $(,)? })?),+ $(,)?) => {
|
||||||
|
const SCHEMA: &[(u32, &str, &[&str])] = &[
|
||||||
|
$(($num, stringify!($name), &[$($(stringify!($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),+)?] => CustomInst::$name $({ $($field: $field.clone()),+ })?,
|
||||||
|
_ => unreachable!("{self:?} does not have the right number of operands"),
|
||||||
|
}),+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum CustomInst<T> {
|
||||||
|
$($name $({ $($field: T),+ })?),+
|
||||||
|
}
|
||||||
|
|
||||||
|
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),+ })? => [$($($field),+)?].into_iter().collect()),+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomInst<Operand> {
|
||||||
|
pub fn decode(inst: &Instruction) -> Self {
|
||||||
|
CustomOp::decode_from_ext_inst(inst).with_operands(&inst.operands[2..])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def_custom_insts! {
|
||||||
|
// `OpLine` equivalent.
|
||||||
|
// FIXME(eddyb) improve on this, by adding more information.
|
||||||
|
0 => SetDebugSrcLoc { file, line, col },
|
||||||
|
// `OpNoLine` equivalent.
|
||||||
|
1 => ClearDebugSrcLoc,
|
||||||
|
}
|
@ -73,6 +73,7 @@ mod builder;
|
|||||||
mod builder_spirv;
|
mod builder_spirv;
|
||||||
mod codegen_cx;
|
mod codegen_cx;
|
||||||
mod custom_decorations;
|
mod custom_decorations;
|
||||||
|
mod custom_insts;
|
||||||
mod link;
|
mod link;
|
||||||
mod linker;
|
mod linker;
|
||||||
mod spirv_type;
|
mod spirv_type;
|
||||||
|
@ -20,6 +20,7 @@ use std::borrow::Cow;
|
|||||||
|
|
||||||
use crate::codegen_cx::SpirvMetadata;
|
use crate::codegen_cx::SpirvMetadata;
|
||||||
use crate::custom_decorations::{CustomDecoration, SrcLocDecoration, ZombieDecoration};
|
use crate::custom_decorations::{CustomDecoration, SrcLocDecoration, ZombieDecoration};
|
||||||
|
use crate::custom_insts;
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use rspirv::binary::{Assemble, Consumer};
|
use rspirv::binary::{Assemble, Consumer};
|
||||||
use rspirv::dr::{Block, Instruction, Loader, Module, ModuleHeader, Operand};
|
use rspirv::dr::{Block, Instruction, Loader, Module, ModuleHeader, Operand};
|
||||||
@ -56,6 +57,7 @@ pub struct Options {
|
|||||||
pub dump_post_merge: Option<PathBuf>,
|
pub dump_post_merge: Option<PathBuf>,
|
||||||
pub dump_post_split: Option<PathBuf>,
|
pub dump_post_split: Option<PathBuf>,
|
||||||
pub dump_spirt_passes: Option<PathBuf>,
|
pub dump_spirt_passes: Option<PathBuf>,
|
||||||
|
pub spirt_keep_custom_debuginfo_in_dumps: bool,
|
||||||
pub specializer_debug: bool,
|
pub specializer_debug: bool,
|
||||||
pub specializer_dump_instances: Option<PathBuf>,
|
pub specializer_dump_instances: Option<PathBuf>,
|
||||||
pub print_all_zombie: bool,
|
pub print_all_zombie: bool,
|
||||||
@ -425,6 +427,15 @@ pub fn link(
|
|||||||
.join(disambiguated_crate_name_for_dumps)
|
.join(disambiguated_crate_name_for_dumps)
|
||||||
.with_extension("spirt");
|
.with_extension("spirt");
|
||||||
|
|
||||||
|
// HACK(eddyb) unless requested otherwise, clean up the pretty-printed
|
||||||
|
// SPIR-T output by converting our custom extended instructions, to
|
||||||
|
// standard SPIR-V debuginfo (which SPIR-T knows how to pretty-print).
|
||||||
|
if !opts.spirt_keep_custom_debuginfo_in_dumps {
|
||||||
|
for (_, module) in &mut per_pass_module_for_dumping {
|
||||||
|
spirt_passes::debuginfo::convert_custom_debuginfo_to_spv(module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let plan = spirt::print::Plan::for_versions(
|
let plan = spirt::print::Plan::for_versions(
|
||||||
&cx,
|
&cx,
|
||||||
per_pass_module_for_dumping
|
per_pass_module_for_dumping
|
||||||
@ -446,10 +457,13 @@ pub fn link(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE(eddyb) this is late so that `--dump-spirt-passes` is processed,
|
// NOTE(eddyb) this is late so that `--dump-spirt-passes` is processed,
|
||||||
// even/especially when error were reported, but lifting to SPIR-V is
|
// even/especially when errors were reported, but lifting to SPIR-V is
|
||||||
// skipped (since it could very well fail due to reported errors).
|
// skipped (since it could very well fail due to reported errors).
|
||||||
report_diagnostics_result?;
|
report_diagnostics_result?;
|
||||||
|
|
||||||
|
// Replace our custom debuginfo instructions just before lifting to SPIR-V.
|
||||||
|
spirt_passes::debuginfo::convert_custom_debuginfo_to_spv(&mut module);
|
||||||
|
|
||||||
let spv_words = {
|
let spv_words = {
|
||||||
let _timer = sess.timer("spirt::Module::lift_to_spv_module_emitter");
|
let _timer = sess.timer("spirt::Module::lift_to_spv_module_emitter");
|
||||||
module.lift_to_spv_module_emitter().unwrap().words
|
module.lift_to_spv_module_emitter().unwrap().words
|
||||||
@ -462,6 +476,26 @@ pub fn link(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure that no references remain, to our custom "extended instruction set".
|
||||||
|
for inst in &output.ext_inst_imports {
|
||||||
|
assert_eq!(inst.class.opcode, Op::ExtInstImport);
|
||||||
|
let ext_inst_set = inst.operands[0].unwrap_literal_string();
|
||||||
|
if ext_inst_set.starts_with(custom_insts::CUSTOM_EXT_INST_SET_PREFIX) {
|
||||||
|
let expected = &custom_insts::CUSTOM_EXT_INST_SET[..];
|
||||||
|
if ext_inst_set == expected {
|
||||||
|
return Err(sess.err(format!(
|
||||||
|
"`OpExtInstImport {ext_inst_set:?}` should not have been \
|
||||||
|
left around after SPIR-T passes"
|
||||||
|
)));
|
||||||
|
} else {
|
||||||
|
return Err(sess.err(format!(
|
||||||
|
"unsupported `OpExtInstImport {ext_inst_set:?}`
|
||||||
|
(expected {expected:?} name - version mismatch?)"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME(eddyb) rewrite these passes to SPIR-T ones, so we don't have to
|
// FIXME(eddyb) rewrite these passes to SPIR-T ones, so we don't have to
|
||||||
// parse the output of `spirt::spv::lift` back into `rspirv` - also, for
|
// parse the output of `spirt::spv::lift` back into `rspirv` - also, for
|
||||||
// multi-module, it's much simpler with SPIR-T, just replace `module.exports`
|
// multi-module, it's much simpler with SPIR-T, just replace `module.exports`
|
||||||
|
@ -143,6 +143,7 @@ pub fn name_variables_pass(module: &mut Module) {
|
|||||||
module
|
module
|
||||||
.debug_names
|
.debug_names
|
||||||
.retain(|inst| variables.contains(&inst.operands[0].unwrap_id_ref()));
|
.retain(|inst| variables.contains(&inst.operands[0].unwrap_id_ref()));
|
||||||
|
// FIXME(eddyb) why does this remove `OpLine` instructions?
|
||||||
module
|
module
|
||||||
.types_global_values
|
.types_global_values
|
||||||
.retain(|inst| inst.class.opcode != Op::Line);
|
.retain(|inst| inst.class.opcode != Op::Line);
|
||||||
|
150
crates/rustc_codegen_spirv/src/linker/spirt_passes/debuginfo.rs
Normal file
150
crates/rustc_codegen_spirv/src/linker/spirt_passes/debuginfo.rs
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
//! SPIR-T passes related to debuginfo.
|
||||||
|
|
||||||
|
use crate::custom_insts::{self, CustomInst, CustomOp};
|
||||||
|
use rustc_data_structures::fx::FxIndexSet;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
use spirt::transform::{InnerInPlaceTransform, Transformer};
|
||||||
|
use spirt::visit::InnerVisit;
|
||||||
|
use spirt::{
|
||||||
|
spv, Attr, AttrSetDef, ConstCtor, Context, ControlNode, ControlNodeKind, DataInstKind,
|
||||||
|
InternedStr, Module, OrdAssertEq, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Replace our custom extended instruction debuginfo with standard SPIR-V ones.
|
||||||
|
//
|
||||||
|
// FIXME(eddyb) also handle `SrcLocDecoration`s (when `rspirv` isn't used on the
|
||||||
|
// SPIR-V output of `spirt::spv::lift`, as it's lossy wrt `OpLine`).
|
||||||
|
pub fn convert_custom_debuginfo_to_spv(module: &mut Module) {
|
||||||
|
let cx = &module.cx();
|
||||||
|
|
||||||
|
// FIXME(eddyb) reuse this collection work in some kind of "pass manager".
|
||||||
|
let all_funcs = {
|
||||||
|
let mut collector = super::ReachableUseCollector {
|
||||||
|
cx,
|
||||||
|
module,
|
||||||
|
|
||||||
|
seen_types: FxIndexSet::default(),
|
||||||
|
seen_consts: FxIndexSet::default(),
|
||||||
|
seen_global_vars: FxIndexSet::default(),
|
||||||
|
seen_funcs: FxIndexSet::default(),
|
||||||
|
};
|
||||||
|
for (export_key, &exportee) in &module.exports {
|
||||||
|
export_key.inner_visit_with(&mut collector);
|
||||||
|
exportee.inner_visit_with(&mut collector);
|
||||||
|
}
|
||||||
|
collector.seen_funcs
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut transformer = CustomDebuginfoToSpv {
|
||||||
|
cx,
|
||||||
|
wk: &super::SpvSpecWithExtras::get().well_known,
|
||||||
|
custom_ext_inst_set: cx.intern(&custom_insts::CUSTOM_EXT_INST_SET[..]),
|
||||||
|
};
|
||||||
|
for func in all_funcs {
|
||||||
|
transformer.in_place_transform_func_decl(&mut module.funcs[func]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CustomDebuginfoToSpv<'a> {
|
||||||
|
cx: &'a Context,
|
||||||
|
wk: &'static super::SpvWellKnownWithExtras,
|
||||||
|
|
||||||
|
/// Interned name for our custom "extended instruction set"
|
||||||
|
/// (see `crate::custom_insts` for more details).
|
||||||
|
custom_ext_inst_set: InternedStr,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Transformer for CustomDebuginfoToSpv<'_> {
|
||||||
|
fn in_place_transform_control_node_def(
|
||||||
|
&mut self,
|
||||||
|
mut func_at_control_node: spirt::func_at::FuncAtMut<'_, ControlNode>,
|
||||||
|
) {
|
||||||
|
// HACK(eddyb) this relies on the fact that `ControlNodeKind::Block` maps
|
||||||
|
// to one original SPIR-V block, which may not necessarily be true, and
|
||||||
|
// steps should be taken elsewhere to explicitly unset debuginfo, instead
|
||||||
|
// of relying on the end of a SPIR-V block implicitly unsetting it all.
|
||||||
|
// NOTE(eddyb) allowing debuginfo to apply *outside* of a `Block` could
|
||||||
|
// be useful in allowing *some* structured control-flow to have debuginfo,
|
||||||
|
// but that would likely require more work on the SPIR-T side.
|
||||||
|
if let ControlNodeKind::Block { mut insts } = func_at_control_node.reborrow().def().kind {
|
||||||
|
let mut current_file_line_col = None;
|
||||||
|
|
||||||
|
// HACK(eddyb) buffering the `DataInst`s to remove from this block,
|
||||||
|
// as iterating and modifying a list at the same time isn't supported.
|
||||||
|
let mut insts_to_remove = SmallVec::<[_; 8]>::new();
|
||||||
|
|
||||||
|
let mut func_at_inst_iter = func_at_control_node.reborrow().at(insts).into_iter();
|
||||||
|
while let Some(func_at_inst) = func_at_inst_iter.next() {
|
||||||
|
let inst = func_at_inst.position;
|
||||||
|
let data_inst_def = func_at_inst.def();
|
||||||
|
|
||||||
|
// FIXME(eddyb) deduplicate with `spirt_passes::diagnostics`.
|
||||||
|
if let DataInstKind::SpvExtInst {
|
||||||
|
ext_set,
|
||||||
|
inst: ext_inst,
|
||||||
|
} = data_inst_def.kind
|
||||||
|
{
|
||||||
|
if ext_set == self.custom_ext_inst_set {
|
||||||
|
match CustomOp::decode(ext_inst).with_operands(&data_inst_def.inputs) {
|
||||||
|
CustomInst::SetDebugSrcLoc { file, line, col } => {
|
||||||
|
let const_ctor = |v: Value| match v {
|
||||||
|
Value::Const(ct) => &self.cx[ct].ctor,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let const_str = |v: Value| match const_ctor(v) {
|
||||||
|
&ConstCtor::SpvStringLiteralForExtInst(s) => s,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let const_u32 = |v: Value| match const_ctor(v) {
|
||||||
|
ConstCtor::SpvInst(spv_inst) => {
|
||||||
|
assert!(spv_inst.opcode == self.wk.OpConstant);
|
||||||
|
match spv_inst.imms[..] {
|
||||||
|
[spv::Imm::Short(_, x)] => x,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
current_file_line_col =
|
||||||
|
Some((const_str(file), const_u32(line), const_u32(col)));
|
||||||
|
insts_to_remove.push(inst);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
CustomInst::ClearDebugSrcLoc => {
|
||||||
|
current_file_line_col = None;
|
||||||
|
insts_to_remove.push(inst);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add/remove the equivalent `Attr::SpvDebugLine` attribute.
|
||||||
|
// FIXME(eddyb) this could use more caching.
|
||||||
|
data_inst_def.attrs = self.cx.intern(AttrSetDef {
|
||||||
|
attrs: self.cx[data_inst_def.attrs]
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.filter(|attr| !matches!(attr, Attr::SpvDebugLine { .. }))
|
||||||
|
.cloned()
|
||||||
|
.chain(
|
||||||
|
current_file_line_col.map(|(file, line, col)| Attr::SpvDebugLine {
|
||||||
|
file_path: OrdAssertEq(file),
|
||||||
|
line,
|
||||||
|
col,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.collect(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally remove the `DataInst`s buffered for removal earlier.
|
||||||
|
for inst in insts_to_remove {
|
||||||
|
insts.remove(inst, func_at_control_node.data_insts);
|
||||||
|
}
|
||||||
|
func_at_control_node.reborrow().def().kind = ControlNodeKind::Block { insts };
|
||||||
|
}
|
||||||
|
|
||||||
|
func_at_control_node.inner_in_place_transform_with(self);
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,18 @@
|
|||||||
use crate::custom_decorations::{CustomDecoration, SpanRegenerator, SrcLocDecoration, ZombieDecoration};
|
use crate::custom_decorations::{
|
||||||
|
CustomDecoration, SpanRegenerator, SrcLocDecoration, ZombieDecoration,
|
||||||
|
};
|
||||||
|
use crate::custom_insts::{self, CustomInst, CustomOp};
|
||||||
use rustc_data_structures::fx::FxIndexSet;
|
use rustc_data_structures::fx::FxIndexSet;
|
||||||
use rustc_errors::DiagnosticBuilder;
|
use rustc_errors::DiagnosticBuilder;
|
||||||
use rustc_session::Session;
|
use rustc_session::Session;
|
||||||
use rustc_span::{Span, DUMMY_SP};
|
use rustc_span::{Span, DUMMY_SP};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
use spirt::func_at::FuncAt;
|
||||||
use spirt::visit::{InnerVisit, Visitor};
|
use spirt::visit::{InnerVisit, Visitor};
|
||||||
use spirt::{
|
use spirt::{
|
||||||
spv, Attr, AttrSet, AttrSetDef, Const, Context, DataInstDef, DataInstKind, Diag, DiagLevel,
|
spv, Attr, AttrSet, AttrSetDef, Const, ConstCtor, Context, ControlNode, ControlNodeKind,
|
||||||
ExportKey, Exportee, Func, GlobalVar, Module, Type,
|
DataInstDef, DataInstKind, Diag, DiagLevel, ExportKey, Exportee, Func, GlobalVar, InternedStr,
|
||||||
|
Module, Type, Value,
|
||||||
};
|
};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::{mem, str};
|
use std::{mem, str};
|
||||||
@ -24,6 +29,8 @@ pub(crate) fn report_diagnostics(
|
|||||||
linker_options,
|
linker_options,
|
||||||
|
|
||||||
cx,
|
cx,
|
||||||
|
custom_ext_inst_set: cx.intern(&custom_insts::CUSTOM_EXT_INST_SET[..]),
|
||||||
|
|
||||||
module,
|
module,
|
||||||
|
|
||||||
seen_attrs: FxIndexSet::default(),
|
seen_attrs: FxIndexSet::default(),
|
||||||
@ -37,14 +44,15 @@ pub(crate) fn report_diagnostics(
|
|||||||
overall_result: Ok(()),
|
overall_result: Ok(()),
|
||||||
any_spirt_bugs: false,
|
any_spirt_bugs: false,
|
||||||
};
|
};
|
||||||
for (export_key, &exportee) in &module.exports {
|
for (export_key, exportee) in &module.exports {
|
||||||
assert_eq!(reporter.use_stack.len(), 0);
|
assert_eq!(reporter.use_stack.len(), 0);
|
||||||
|
|
||||||
if let Exportee::Func(func) = exportee {
|
if let &Exportee::Func(func) = exportee {
|
||||||
let func_decl = &module.funcs[func];
|
let func_decl = &module.funcs[func];
|
||||||
reporter.use_stack.push(UseOrigin::IntraFunc {
|
reporter.use_stack.push(UseOrigin::IntraFunc {
|
||||||
func_attrs: func_decl.attrs,
|
func_attrs: func_decl.attrs,
|
||||||
func_export_key: Some(export_key),
|
func_export_key: Some(export_key),
|
||||||
|
last_debug_src_loc_inst: None,
|
||||||
inst_attrs: AttrSet::default(),
|
inst_attrs: AttrSet::default(),
|
||||||
origin: IntraFuncUseOrigin::Other,
|
origin: IntraFuncUseOrigin::Other,
|
||||||
});
|
});
|
||||||
@ -149,6 +157,11 @@ struct DiagnosticReporter<'a> {
|
|||||||
linker_options: &'a crate::linker::Options,
|
linker_options: &'a crate::linker::Options,
|
||||||
|
|
||||||
cx: &'a Context,
|
cx: &'a Context,
|
||||||
|
|
||||||
|
/// Interned name for our custom "extended instruction set"
|
||||||
|
/// (see `crate::custom_insts` for more details).
|
||||||
|
custom_ext_inst_set: InternedStr,
|
||||||
|
|
||||||
module: &'a Module,
|
module: &'a Module,
|
||||||
|
|
||||||
seen_attrs: FxIndexSet<AttrSet>,
|
seen_attrs: FxIndexSet<AttrSet>,
|
||||||
@ -172,6 +185,10 @@ enum UseOrigin<'a> {
|
|||||||
func_attrs: AttrSet,
|
func_attrs: AttrSet,
|
||||||
func_export_key: Option<&'a ExportKey>,
|
func_export_key: Option<&'a ExportKey>,
|
||||||
|
|
||||||
|
/// Active debug "source location" instruction at the time of the use, if any
|
||||||
|
/// (only `CustomInst::SetDebugSrcLoc` is supported).
|
||||||
|
last_debug_src_loc_inst: Option<&'a DataInstDef>,
|
||||||
|
|
||||||
inst_attrs: AttrSet,
|
inst_attrs: AttrSet,
|
||||||
origin: IntraFuncUseOrigin,
|
origin: IntraFuncUseOrigin,
|
||||||
},
|
},
|
||||||
@ -184,7 +201,7 @@ enum IntraFuncUseOrigin {
|
|||||||
|
|
||||||
impl UseOrigin<'_> {
|
impl UseOrigin<'_> {
|
||||||
fn to_rustc_span(&self, cx: &Context, span_regen: &mut SpanRegenerator<'_>) -> Option<Span> {
|
fn to_rustc_span(&self, cx: &Context, span_regen: &mut SpanRegenerator<'_>) -> Option<Span> {
|
||||||
let mut from_attrs = |attrs: AttrSet| {
|
let from_attrs = |attrs: AttrSet, span_regen: &mut SpanRegenerator<'_>| {
|
||||||
let attrs_def = &cx[attrs];
|
let attrs_def = &cx[attrs];
|
||||||
attrs_def
|
attrs_def
|
||||||
.attrs
|
.attrs
|
||||||
@ -208,12 +225,62 @@ impl UseOrigin<'_> {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
match *self {
|
match *self {
|
||||||
Self::Global { attrs, .. } => from_attrs(attrs),
|
Self::Global { attrs, .. } => from_attrs(attrs, span_regen),
|
||||||
Self::IntraFunc {
|
Self::IntraFunc {
|
||||||
func_attrs,
|
func_attrs,
|
||||||
|
last_debug_src_loc_inst,
|
||||||
inst_attrs,
|
inst_attrs,
|
||||||
..
|
..
|
||||||
} => from_attrs(inst_attrs).or_else(|| from_attrs(func_attrs)),
|
} => from_attrs(inst_attrs, span_regen)
|
||||||
|
.or_else(|| {
|
||||||
|
let debug_inst_def = last_debug_src_loc_inst?;
|
||||||
|
|
||||||
|
let wk = &super::SpvSpecWithExtras::get().well_known;
|
||||||
|
|
||||||
|
// FIXME(eddyb) deduplicate with `spirt_passes::diagnostics`.
|
||||||
|
let custom_op = match debug_inst_def.kind {
|
||||||
|
DataInstKind::SpvExtInst {
|
||||||
|
ext_set,
|
||||||
|
inst: ext_inst,
|
||||||
|
} => {
|
||||||
|
// FIXME(eddyb) inefficient (ideally the `InternedStr`
|
||||||
|
// shoudl be available), but this is the error case.
|
||||||
|
assert_eq!(&cx[ext_set], &custom_insts::CUSTOM_EXT_INST_SET[..]);
|
||||||
|
|
||||||
|
CustomOp::decode(ext_inst)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let (file, line, col) = match custom_op.with_operands(&debug_inst_def.inputs) {
|
||||||
|
CustomInst::SetDebugSrcLoc { file, line, col } => (file, line, col),
|
||||||
|
CustomInst::ClearDebugSrcLoc => unreachable!(),
|
||||||
|
};
|
||||||
|
let const_ctor = |v: Value| match v {
|
||||||
|
Value::Const(ct) => &cx[ct].ctor,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let const_str = |v: Value| match const_ctor(v) {
|
||||||
|
&ConstCtor::SpvStringLiteralForExtInst(s) => s,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let const_u32 = |v: Value| match const_ctor(v) {
|
||||||
|
ConstCtor::SpvInst(spv_inst) => {
|
||||||
|
assert!(spv_inst.opcode == wk.OpConstant);
|
||||||
|
match spv_inst.imms[..] {
|
||||||
|
[spv::Imm::Short(_, x)] => x,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
span_regen.src_loc_to_rustc(SrcLocDecoration {
|
||||||
|
file_name: &cx[const_str(file)],
|
||||||
|
line: const_u32(line),
|
||||||
|
col: const_u32(col),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.or_else(|| from_attrs(func_attrs, span_regen)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,6 +313,7 @@ impl UseOrigin<'_> {
|
|||||||
Self::IntraFunc {
|
Self::IntraFunc {
|
||||||
func_attrs,
|
func_attrs,
|
||||||
func_export_key,
|
func_export_key,
|
||||||
|
last_debug_src_loc_inst: _,
|
||||||
inst_attrs: _,
|
inst_attrs: _,
|
||||||
origin,
|
origin,
|
||||||
} => {
|
} => {
|
||||||
@ -375,7 +443,7 @@ impl DiagnosticReporter<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Visitor<'_> for DiagnosticReporter<'_> {
|
impl<'a> Visitor<'a> for DiagnosticReporter<'a> {
|
||||||
fn visit_attr_set_use(&mut self, attrs: AttrSet) {
|
fn visit_attr_set_use(&mut self, attrs: AttrSet) {
|
||||||
// HACK(eddyb) this avoids reporting the same diagnostics more than once.
|
// HACK(eddyb) this avoids reporting the same diagnostics more than once.
|
||||||
if self.seen_attrs.insert(attrs) {
|
if self.seen_attrs.insert(attrs) {
|
||||||
@ -424,6 +492,7 @@ impl Visitor<'_> for DiagnosticReporter<'_> {
|
|||||||
self.use_stack.push(UseOrigin::IntraFunc {
|
self.use_stack.push(UseOrigin::IntraFunc {
|
||||||
func_attrs: func_decl.attrs,
|
func_attrs: func_decl.attrs,
|
||||||
func_export_key: None,
|
func_export_key: None,
|
||||||
|
last_debug_src_loc_inst: None,
|
||||||
inst_attrs: AttrSet::default(),
|
inst_attrs: AttrSet::default(),
|
||||||
origin: IntraFuncUseOrigin::Other,
|
origin: IntraFuncUseOrigin::Other,
|
||||||
});
|
});
|
||||||
@ -432,9 +501,54 @@ impl Visitor<'_> for DiagnosticReporter<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_data_inst_def(&mut self, data_inst_def: &DataInstDef) {
|
fn visit_control_node_def(&mut self, func_at_control_node: FuncAt<'a, ControlNode>) {
|
||||||
|
func_at_control_node.inner_visit_with(self);
|
||||||
|
|
||||||
|
// HACK(eddyb) this relies on the fact that `ControlNodeKind::Block` maps
|
||||||
|
// to one original SPIR-V block, which may not necessarily be true, and
|
||||||
|
// steps should be taken elsewhere to explicitly unset debuginfo, instead
|
||||||
|
// of relying on the end of a SPIR-V block implicitly unsetting it all.
|
||||||
|
// NOTE(eddyb) allowing debuginfo to apply *outside* of a `Block` could
|
||||||
|
// be useful in allowing *some* structured control-flow to have debuginfo,
|
||||||
|
// but that would likely require more work on the SPIR-T side.
|
||||||
|
if let ControlNodeKind::Block { .. } = func_at_control_node.def().kind {
|
||||||
|
match self.use_stack.last_mut() {
|
||||||
|
Some(UseOrigin::IntraFunc {
|
||||||
|
last_debug_src_loc_inst,
|
||||||
|
..
|
||||||
|
}) => *last_debug_src_loc_inst = None,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_data_inst_def(&mut self, data_inst_def: &'a DataInstDef) {
|
||||||
match self.use_stack.last_mut() {
|
match self.use_stack.last_mut() {
|
||||||
Some(UseOrigin::IntraFunc { inst_attrs, .. }) => *inst_attrs = data_inst_def.attrs,
|
Some(UseOrigin::IntraFunc {
|
||||||
|
inst_attrs,
|
||||||
|
last_debug_src_loc_inst,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
*inst_attrs = data_inst_def.attrs;
|
||||||
|
|
||||||
|
// FIXME(eddyb) deduplicate with `spirt_passes::debuginfo`.
|
||||||
|
if let DataInstKind::SpvExtInst {
|
||||||
|
ext_set,
|
||||||
|
inst: ext_inst,
|
||||||
|
} = data_inst_def.kind
|
||||||
|
{
|
||||||
|
if ext_set == self.custom_ext_inst_set {
|
||||||
|
match CustomOp::decode(ext_inst) {
|
||||||
|
CustomOp::SetDebugSrcLoc => {
|
||||||
|
*last_debug_src_loc_inst = Some(data_inst_def);
|
||||||
|
}
|
||||||
|
CustomOp::ClearDebugSrcLoc => {
|
||||||
|
*last_debug_src_loc_inst = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
//! SPIR-T pass infrastructure and supporting utilities.
|
//! SPIR-T pass infrastructure and supporting utilities.
|
||||||
|
|
||||||
|
pub(crate) mod debuginfo;
|
||||||
pub(crate) mod diagnostics;
|
pub(crate) mod diagnostics;
|
||||||
mod fuse_selects;
|
mod fuse_selects;
|
||||||
mod reduce;
|
mod reduce;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use super::{get_name, get_names};
|
use super::{get_name, get_names};
|
||||||
use crate::custom_decorations::{CustomDecoration, SpanRegenerator, ZombieDecoration};
|
use crate::custom_decorations::{CustomDecoration, SpanRegenerator, ZombieDecoration};
|
||||||
|
use crate::custom_insts::{self, CustomOp};
|
||||||
use rspirv::dr::{Instruction, Module, Operand};
|
use rspirv::dr::{Instruction, Module, Operand};
|
||||||
use rspirv::spirv::{Op, Word};
|
use rspirv::spirv::{Op, Word};
|
||||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
|
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
|
||||||
@ -27,7 +28,8 @@ enum ZombieKind<'a> {
|
|||||||
struct ZombieUse<'a> {
|
struct ZombieUse<'a> {
|
||||||
used_zombie_id: Word,
|
used_zombie_id: Word,
|
||||||
|
|
||||||
/// Active `OpLine` instruction at the time of the use, if any.
|
/// Active debug "source location" instruction at the time of the use, if any
|
||||||
|
/// (both `OpLine` and `CustomInst::SetDebugSrcLoc` are supported).
|
||||||
use_debug_src_loc_inst: Option<&'a Instruction>,
|
use_debug_src_loc_inst: Option<&'a Instruction>,
|
||||||
|
|
||||||
origin: UseOrigin,
|
origin: UseOrigin,
|
||||||
@ -40,6 +42,10 @@ enum UseOrigin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Zombies<'a> {
|
struct Zombies<'a> {
|
||||||
|
/// ID of `OpExtInstImport` for our custom "extended instruction set",
|
||||||
|
/// if present (see `crate::custom_insts` for more details).
|
||||||
|
custom_ext_inst_set_import: Option<Word>,
|
||||||
|
|
||||||
id_to_zombie_kind: FxIndexMap<Word, ZombieKind<'a>>,
|
id_to_zombie_kind: FxIndexMap<Word, ZombieKind<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +76,15 @@ impl<'a> Zombies<'a> {
|
|||||||
match inst.class.opcode {
|
match inst.class.opcode {
|
||||||
Op::Line => debug_src_loc_inst = Some(inst),
|
Op::Line => debug_src_loc_inst = Some(inst),
|
||||||
Op::NoLine => debug_src_loc_inst = None,
|
Op::NoLine => debug_src_loc_inst = None,
|
||||||
|
Op::ExtInst
|
||||||
|
if Some(inst.operands[0].unwrap_id_ref())
|
||||||
|
== self.custom_ext_inst_set_import =>
|
||||||
|
{
|
||||||
|
match CustomOp::decode_from_ext_inst(inst) {
|
||||||
|
CustomOp::SetDebugSrcLoc => debug_src_loc_inst = Some(inst),
|
||||||
|
CustomOp::ClearDebugSrcLoc => debug_src_loc_inst = None,
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,6 +128,15 @@ impl<'a> Zombies<'a> {
|
|||||||
Op::Line => debug_src_loc_inst = Some(inst),
|
Op::Line => debug_src_loc_inst = Some(inst),
|
||||||
// NOTE(eddyb) each block starts out with cleared debuginfo.
|
// NOTE(eddyb) each block starts out with cleared debuginfo.
|
||||||
Op::Label | Op::NoLine => debug_src_loc_inst = None,
|
Op::Label | Op::NoLine => debug_src_loc_inst = None,
|
||||||
|
Op::ExtInst
|
||||||
|
if Some(inst.operands[0].unwrap_id_ref())
|
||||||
|
== self.custom_ext_inst_set_import =>
|
||||||
|
{
|
||||||
|
match CustomOp::decode_from_ext_inst(inst) {
|
||||||
|
CustomOp::SetDebugSrcLoc => debug_src_loc_inst = Some(inst),
|
||||||
|
CustomOp::ClearDebugSrcLoc => debug_src_loc_inst = None,
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,6 +325,16 @@ pub fn report_and_remove_zombies(
|
|||||||
module: &mut Module,
|
module: &mut Module,
|
||||||
) -> super::Result<()> {
|
) -> super::Result<()> {
|
||||||
let mut zombies = Zombies {
|
let mut zombies = Zombies {
|
||||||
|
// FIXME(eddyb) avoid repeating this across different passes/helpers.
|
||||||
|
custom_ext_inst_set_import: module
|
||||||
|
.ext_inst_imports
|
||||||
|
.iter()
|
||||||
|
.find(|inst| {
|
||||||
|
assert_eq!(inst.class.opcode, Op::ExtInstImport);
|
||||||
|
inst.operands[0].unwrap_literal_string() == &custom_insts::CUSTOM_EXT_INST_SET[..]
|
||||||
|
})
|
||||||
|
.map(|inst| inst.result_id.unwrap()),
|
||||||
|
|
||||||
id_to_zombie_kind: ZombieDecoration::decode_all(module)
|
id_to_zombie_kind: ZombieDecoration::decode_all(module)
|
||||||
.map(|(id, _)| (id, ZombieKind::Leaf))
|
.map(|(id, _)| (id, ZombieKind::Leaf))
|
||||||
.collect(),
|
.collect(),
|
||||||
|
@ -191,3 +191,12 @@ _Note: passes that are not already enabled by default are considered experimenta
|
|||||||
|
|
||||||
Dump the `SPIR-🇹` module across passes (i.e. all of the versions before/after each pass), as a combined report, to a pair of files (`.spirt` and `.spirt.html`) in `DIR`.
|
Dump the `SPIR-🇹` module across passes (i.e. all of the versions before/after each pass), as a combined report, to a pair of files (`.spirt` and `.spirt.html`) in `DIR`.
|
||||||
<sub>(the `.spirt.html` version of the report is the recommended form for viewing, as it uses tabling for versions, syntax-highlighting-like styling, and use->def linking)</sub>
|
<sub>(the `.spirt.html` version of the report is the recommended form for viewing, as it uses tabling for versions, syntax-highlighting-like styling, and use->def linking)</sub>
|
||||||
|
|
||||||
|
### `--spirt-keep-custom-debuginfo-in-dumps`
|
||||||
|
|
||||||
|
When dumping (pretty-printed) `SPIR-🇹` (e.g. with `--dump-spirt-passes`), preserve
|
||||||
|
all the custom (Rust-GPU-specific) debuginfo instructions, instead of converting
|
||||||
|
them to the standard SPIR-V debuginfo (which `SPIR-🇹` pretty-prints specially).
|
||||||
|
|
||||||
|
The default (of performing that conversion) has prettier results, but is lossier
|
||||||
|
if you want to see all instructions exactly as e.g. `--spirt-passes` see them.
|
||||||
|
Loading…
Reference in New Issue
Block a user