mirror of
https://github.com/EmbarkStudios/rust-gpu.git
synced 2025-02-16 08:54:56 +00:00
linker/zombies: report all reachable zombies, w/ OpLine
-based stack traces.
This commit is contained in:
parent
3bbfaf5221
commit
633dff18bd
@ -2,6 +2,7 @@ use super::CodegenCx;
|
||||
use crate::abi::ConvSpirvType;
|
||||
use crate::attr::AggregatedSpirvAttributes;
|
||||
use crate::builder_spirv::{SpirvConst, SpirvValue, SpirvValueExt};
|
||||
use crate::decorations::{CustomDecoration, SrcLocDecoration};
|
||||
use crate::spirv_type::SpirvType;
|
||||
use rspirv::spirv::{FunctionControl, LinkageType, StorageClass, Word};
|
||||
use rustc_attr::InlineAttr;
|
||||
@ -74,20 +75,36 @@ impl<'tcx> CodegenCx<'tcx> {
|
||||
self.zombie_with_span(result.def_cx(self), span, "called blocklisted fn");
|
||||
return result;
|
||||
}
|
||||
let mut emit = self.emit_global();
|
||||
let fn_id = emit
|
||||
.begin_function(return_type, None, control, function_type)
|
||||
.unwrap();
|
||||
if linkage != Some(LinkageType::Import) {
|
||||
let parameter_values = argument_types
|
||||
.iter()
|
||||
.map(|&ty| emit.function_parameter(ty).unwrap().with_type(ty))
|
||||
.collect::<Vec<_>>();
|
||||
self.function_parameter_values
|
||||
.borrow_mut()
|
||||
.insert(fn_id, parameter_values);
|
||||
}
|
||||
emit.end_function().unwrap();
|
||||
let fn_id = {
|
||||
let mut emit = self.emit_global();
|
||||
let fn_id = emit
|
||||
.begin_function(return_type, None, control, function_type)
|
||||
.unwrap();
|
||||
if linkage != Some(LinkageType::Import) {
|
||||
let parameter_values = argument_types
|
||||
.iter()
|
||||
.map(|&ty| emit.function_parameter(ty).unwrap().with_type(ty))
|
||||
.collect::<Vec<_>>();
|
||||
self.function_parameter_values
|
||||
.borrow_mut()
|
||||
.insert(fn_id, parameter_values);
|
||||
}
|
||||
emit.end_function().unwrap();
|
||||
fn_id
|
||||
};
|
||||
|
||||
// HACK(eddyb) this is a temporary workaround due to our use of `rspirv`,
|
||||
// which prevents us from attaching `OpLine`s to `OpFunction` definitions,
|
||||
// but we can use our custom `SrcLocDecoration` instead.
|
||||
let src_loc_inst = SrcLocDecoration::from_rustc_span(
|
||||
self.tcx.def_ident_span(instance.def_id()).unwrap_or(span),
|
||||
&self.builder,
|
||||
)
|
||||
.map(|src_loc| src_loc.encode_to_inst(fn_id));
|
||||
self.emit_global()
|
||||
.module_mut()
|
||||
.annotations
|
||||
.extend(src_loc_inst);
|
||||
|
||||
// HACK(eddyb) this is a bit roundabout, but the easiest way to get a
|
||||
// fully absolute path that contains at least as much information as
|
||||
@ -99,9 +116,8 @@ impl<'tcx> CodegenCx<'tcx> {
|
||||
// (as some sort of opt-in, or toggled based on the platform, etc.).
|
||||
let symbol_name = self.tcx.symbol_name(instance).name;
|
||||
let demangled_symbol_name = format!("{:#}", rustc_demangle::demangle(symbol_name));
|
||||
emit.name(fn_id, &demangled_symbol_name);
|
||||
self.emit_global().name(fn_id, &demangled_symbol_name);
|
||||
|
||||
drop(emit); // set_linkage uses emit
|
||||
if let Some(linkage) = linkage {
|
||||
self.set_linkage(fn_id, symbol_name.to_owned(), linkage);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use crate::builder_spirv::BuilderSpirv;
|
||||
use itertools::Itertools;
|
||||
use rspirv::dr::{Instruction, Module, Operand};
|
||||
use rspirv::spirv::{Decoration, Op, Word};
|
||||
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
|
||||
use rustc_data_structures::fx::FxIndexMap;
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_span::{source_map::SourceMap, Span};
|
||||
use rustc_span::{FileName, SourceFile};
|
||||
@ -193,7 +193,53 @@ pub struct SpanRegenerator<'a> {
|
||||
zombie_decorations: Option<FxIndexMap<Word, LazilyDecoded<'a, ZombieDecoration<'a>>>>,
|
||||
|
||||
// HACK(eddyb) this is mostly replicating SPIR-T's module-level debuginfo.
|
||||
spv_debug_files: Option<FxIndexMap<&'a str, SpvDebugFile<'a>>>,
|
||||
spv_debug_info: Option<SpvDebugInfo<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SpvDebugInfo<'a> {
|
||||
id_to_op_string: FxIndexMap<Word, &'a str>,
|
||||
files: FxIndexMap<&'a str, SpvDebugFile<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> SpvDebugInfo<'a> {
|
||||
fn collect(module: &'a Module) -> Self {
|
||||
let mut this = Self::default();
|
||||
let mut insts = module.debug_string_source.iter().peekable();
|
||||
while let Some(inst) = insts.next() {
|
||||
match inst.class.opcode {
|
||||
Op::String => {
|
||||
this.id_to_op_string.insert(
|
||||
inst.result_id.unwrap(),
|
||||
inst.operands[0].unwrap_literal_string(),
|
||||
);
|
||||
}
|
||||
Op::Source if inst.operands.len() == 4 => {
|
||||
let file_name_id = inst.operands[2].unwrap_id_ref();
|
||||
if let Some(&file_name) = this.id_to_op_string.get(&file_name_id) {
|
||||
let mut file = SpvDebugFile::default();
|
||||
file.op_source_parts
|
||||
.push(inst.operands[3].unwrap_literal_string());
|
||||
while let Some(&next_inst) = insts.peek() {
|
||||
if next_inst.class.opcode != Op::SourceContinued {
|
||||
break;
|
||||
}
|
||||
insts.next();
|
||||
|
||||
file.op_source_parts
|
||||
.push(next_inst.operands[0].unwrap_literal_string());
|
||||
}
|
||||
|
||||
// FIXME(eddyb) what if the file is already present,
|
||||
// should it be considered ambiguous overall?
|
||||
this.files.insert(file_name, file);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
// HACK(eddyb) this is mostly replicating SPIR-T's module-level debuginfo.
|
||||
@ -214,7 +260,7 @@ impl<'a> SpanRegenerator<'a> {
|
||||
src_loc_decorations: None,
|
||||
zombie_decorations: None,
|
||||
|
||||
spv_debug_files: None,
|
||||
spv_debug_info: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,46 +280,29 @@ impl<'a> SpanRegenerator<'a> {
|
||||
.map(|zombie| zombie.decode())
|
||||
}
|
||||
|
||||
pub fn src_loc_from_op_line(
|
||||
&mut self,
|
||||
file_id: Word,
|
||||
line: u32,
|
||||
col: u32,
|
||||
) -> Option<SrcLocDecoration<'a>> {
|
||||
self.spv_debug_info
|
||||
.get_or_insert_with(|| SpvDebugInfo::collect(self.module))
|
||||
.id_to_op_string
|
||||
.get(&file_id)
|
||||
.map(|&file_name| SrcLocDecoration {
|
||||
file_name,
|
||||
line,
|
||||
col,
|
||||
})
|
||||
}
|
||||
|
||||
fn regenerate_rustc_source_file(&mut self, file_name: &str) -> Option<&SourceFile> {
|
||||
let spv_debug_files = self.spv_debug_files.get_or_insert_with(|| {
|
||||
let mut op_string_by_id = FxHashMap::default();
|
||||
let mut spv_debug_files = FxIndexMap::default();
|
||||
let mut insts = self.module.debug_string_source.iter().peekable();
|
||||
while let Some(inst) = insts.next() {
|
||||
match inst.class.opcode {
|
||||
Op::String => {
|
||||
op_string_by_id.insert(
|
||||
inst.result_id.unwrap(),
|
||||
inst.operands[0].unwrap_literal_string(),
|
||||
);
|
||||
}
|
||||
Op::Source if inst.operands.len() == 4 => {
|
||||
let file_name_id = inst.operands[2].unwrap_id_ref();
|
||||
if let Some(&file_name) = op_string_by_id.get(&file_name_id) {
|
||||
let mut file = SpvDebugFile::default();
|
||||
file.op_source_parts
|
||||
.push(inst.operands[3].unwrap_literal_string());
|
||||
while let Some(&next_inst) = insts.peek() {
|
||||
if next_inst.class.opcode != Op::SourceContinued {
|
||||
break;
|
||||
}
|
||||
insts.next();
|
||||
|
||||
file.op_source_parts
|
||||
.push(next_inst.operands[0].unwrap_literal_string());
|
||||
}
|
||||
|
||||
// FIXME(eddyb) what if the file is already present,
|
||||
// should it be considered ambiguous overall?
|
||||
spv_debug_files.insert(file_name, file);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
spv_debug_files
|
||||
});
|
||||
let spv_debug_file = spv_debug_files.get_mut(file_name)?;
|
||||
let spv_debug_file = self
|
||||
.spv_debug_info
|
||||
.get_or_insert_with(|| SpvDebugInfo::collect(self.module))
|
||||
.files
|
||||
.get_mut(file_name)?;
|
||||
|
||||
let file = &mut spv_debug_file.regenerated_rustc_source_file;
|
||||
if file.is_none() {
|
||||
|
@ -2,140 +2,310 @@
|
||||
|
||||
use super::{get_name, get_names};
|
||||
use crate::decorations::{CustomDecoration, SpanRegenerator, ZombieDecoration};
|
||||
use rspirv::dr::{Instruction, Module};
|
||||
use rspirv::dr::{Instruction, Module, Operand};
|
||||
use rspirv::spirv::{Op, Word};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
|
||||
use rustc_errors::{DiagnosticBuilder, ErrorGuaranteed};
|
||||
use rustc_session::Session;
|
||||
use rustc_span::DUMMY_SP;
|
||||
use std::iter::once;
|
||||
use rustc_span::{Span, DUMMY_SP};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
// FIXME(eddyb) change this to chain through IDs instead of wasting allocations.
|
||||
#[derive(Clone)]
|
||||
struct Zombie {
|
||||
leaf_id: Word,
|
||||
stack: Vec<Word>,
|
||||
#[derive(Copy, Clone)]
|
||||
struct Zombie<'a> {
|
||||
id: Word,
|
||||
kind: &'a ZombieKind,
|
||||
}
|
||||
|
||||
impl Zombie {
|
||||
fn push_stack(&self, word: Word) -> Self {
|
||||
Self {
|
||||
leaf_id: self.leaf_id,
|
||||
stack: self.stack.iter().cloned().chain(once(word)).collect(),
|
||||
}
|
||||
enum ZombieKind {
|
||||
/// Definition annotated with `ZombieDecoration`.
|
||||
Leaf,
|
||||
|
||||
/// Transitively zombie'd by using other zombies, from an instruction.
|
||||
Uses(Vec<ZombieUse>),
|
||||
}
|
||||
|
||||
struct ZombieUse {
|
||||
used_zombie_id: Word,
|
||||
|
||||
/// Operands of the active `OpLine` at the time of the use, if any.
|
||||
use_file_id_line_col: Option<(Word, u32, u32)>,
|
||||
|
||||
origin: UseOrigin,
|
||||
}
|
||||
|
||||
enum UseOrigin {
|
||||
GlobalOperandOrResultType,
|
||||
IntraFuncOperandOrResultType { parent_func_id: Word },
|
||||
CallCalleeOperand { caller_func_id: Word },
|
||||
}
|
||||
|
||||
struct Zombies {
|
||||
id_to_zombie_kind: FxIndexMap<Word, ZombieKind>,
|
||||
}
|
||||
|
||||
impl Zombies {
|
||||
// FIXME(eddyb) rename all the other methods to say `_inst` explicitly.
|
||||
fn get_zombie_by_id(&self, id: Word) -> Option<Zombie<'_>> {
|
||||
self.id_to_zombie_kind
|
||||
.get(&id)
|
||||
.map(|kind| Zombie { id, kind })
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_zombie<'h>(
|
||||
inst: &Instruction,
|
||||
zombies: &'h FxHashMap<Word, Zombie>,
|
||||
) -> Option<&'h Zombie> {
|
||||
if let Some(result_type) = inst.result_type {
|
||||
if let Some(reason) = zombies.get(&result_type) {
|
||||
return Some(reason);
|
||||
}
|
||||
fn zombies_used_from_inst<'a>(
|
||||
&'a self,
|
||||
inst: &'a Instruction,
|
||||
) -> impl Iterator<Item = Zombie<'a>> + 'a {
|
||||
inst.result_type
|
||||
.into_iter()
|
||||
.chain(inst.operands.iter().filter_map(|op| op.id_ref_any()))
|
||||
.filter_map(move |id| self.get_zombie_by_id(id))
|
||||
}
|
||||
inst.operands
|
||||
.iter()
|
||||
.find_map(|op| op.id_ref_any().and_then(|w| zombies.get(&w)))
|
||||
}
|
||||
|
||||
fn is_zombie<'h>(inst: &Instruction, zombies: &'h FxHashMap<Word, Zombie>) -> Option<&'h Zombie> {
|
||||
if let Some(result_id) = inst.result_id {
|
||||
zombies.get(&result_id)
|
||||
} else {
|
||||
contains_zombie(inst, zombies)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_or_contains_zombie<'h>(
|
||||
inst: &Instruction,
|
||||
zombies: &'h FxHashMap<Word, Zombie>,
|
||||
) -> Option<&'h Zombie> {
|
||||
let result_zombie = inst.result_id.and_then(|result_id| zombies.get(&result_id));
|
||||
result_zombie.or_else(|| contains_zombie(inst, zombies))
|
||||
}
|
||||
|
||||
fn spread_zombie(module: &Module, zombies: &mut FxHashMap<Word, Zombie>) -> bool {
|
||||
let mut any = false;
|
||||
// globals are easy
|
||||
for inst in module.global_inst_iter() {
|
||||
if let Some(result_id) = inst.result_id {
|
||||
if let Some(reason) = contains_zombie(inst, zombies) {
|
||||
if zombies.contains_key(&result_id) {
|
||||
continue;
|
||||
fn spread(&mut self, module: &Module) -> bool {
|
||||
let mut any = false;
|
||||
// globals are easy
|
||||
{
|
||||
let mut file_id_line_col = None;
|
||||
for inst in module.global_inst_iter() {
|
||||
match inst.class.opcode {
|
||||
Op::Line => {
|
||||
file_id_line_col = Some((
|
||||
inst.operands[0].unwrap_id_ref(),
|
||||
inst.operands[1].unwrap_literal_int32(),
|
||||
inst.operands[2].unwrap_literal_int32(),
|
||||
));
|
||||
}
|
||||
Op::NoLine => file_id_line_col = None,
|
||||
_ => {}
|
||||
}
|
||||
let reason = reason.clone();
|
||||
zombies.insert(result_id, reason);
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// No need to zombie defs within a function: If any def within a function is zombied, then the
|
||||
// whole function is zombied. But, we don't have to mark the defs within a function as zombie,
|
||||
// because the defs can't escape the function.
|
||||
// HACK(eddyb) one exception to this is function-local variables, which may
|
||||
// be unused and as such cannot be allowed to always zombie the function.
|
||||
for func in &module.functions {
|
||||
let func_id = func.def_id().unwrap();
|
||||
// Can't use zombie.entry() here, due to using the map in contains_zombie
|
||||
if zombies.contains_key(&func_id) {
|
||||
// Func is already zombie, no need to scan it again.
|
||||
continue;
|
||||
}
|
||||
for inst in func.all_inst_iter() {
|
||||
if inst.class.opcode == Op::Variable {
|
||||
let result_id = inst.result_id.unwrap();
|
||||
if let Some(reason) = contains_zombie(inst, zombies) {
|
||||
if zombies.contains_key(&result_id) {
|
||||
|
||||
if let Some(result_id) = inst.result_id {
|
||||
if self.id_to_zombie_kind.contains_key(&result_id) {
|
||||
continue;
|
||||
}
|
||||
let reason = reason.clone();
|
||||
zombies.insert(result_id, reason);
|
||||
any = true;
|
||||
let zombie_uses: Vec<_> = self
|
||||
.zombies_used_from_inst(inst)
|
||||
.map(|zombie| ZombieUse {
|
||||
used_zombie_id: zombie.id,
|
||||
use_file_id_line_col: file_id_line_col,
|
||||
origin: UseOrigin::GlobalOperandOrResultType,
|
||||
})
|
||||
.collect();
|
||||
if !zombie_uses.is_empty() {
|
||||
self.id_to_zombie_kind
|
||||
.insert(result_id, ZombieKind::Uses(zombie_uses));
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
} else if let Some(reason) = is_or_contains_zombie(inst, zombies) {
|
||||
let pushed_reason = reason.push_stack(func_id);
|
||||
zombies.insert(func_id, pushed_reason);
|
||||
any = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// No need to zombie defs within a function: If any def within a function is zombied, then the
|
||||
// whole function is zombied. But, we don't have to mark the defs within a function as zombie,
|
||||
// because the defs can't escape the function.
|
||||
// HACK(eddyb) one exception to this is function-local variables, which may
|
||||
// be unused and as such cannot be allowed to always zombie the function.
|
||||
for func in &module.functions {
|
||||
let func_id = func.def_id().unwrap();
|
||||
if self.id_to_zombie_kind.contains_key(&func_id) {
|
||||
// Func is already zombie, no need to scan it again.
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut all_zombie_uses_in_func = vec![];
|
||||
let mut file_id_line_col = None;
|
||||
for inst in func.all_inst_iter() {
|
||||
match inst.class.opcode {
|
||||
Op::Line => {
|
||||
file_id_line_col = Some((
|
||||
inst.operands[0].unwrap_id_ref(),
|
||||
inst.operands[1].unwrap_literal_int32(),
|
||||
inst.operands[2].unwrap_literal_int32(),
|
||||
));
|
||||
}
|
||||
Op::NoLine => file_id_line_col = None,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if inst.class.opcode == Op::Variable {
|
||||
let result_id = inst.result_id.unwrap();
|
||||
if self.id_to_zombie_kind.contains_key(&result_id) {
|
||||
continue;
|
||||
}
|
||||
let zombie_uses: Vec<_> = self
|
||||
.zombies_used_from_inst(inst)
|
||||
.map(|zombie| ZombieUse {
|
||||
used_zombie_id: zombie.id,
|
||||
use_file_id_line_col: file_id_line_col,
|
||||
origin: UseOrigin::IntraFuncOperandOrResultType {
|
||||
parent_func_id: func_id,
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
if !zombie_uses.is_empty() {
|
||||
self.id_to_zombie_kind
|
||||
.insert(result_id, ZombieKind::Uses(zombie_uses));
|
||||
any = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
all_zombie_uses_in_func.extend(
|
||||
inst.result_id
|
||||
.and_then(|result_id| self.get_zombie_by_id(result_id))
|
||||
.into_iter()
|
||||
.chain(self.zombies_used_from_inst(inst))
|
||||
.map(|zombie| {
|
||||
let origin = if inst.class.opcode == Op::FunctionCall
|
||||
&& inst.operands[0] == Operand::IdRef(zombie.id)
|
||||
{
|
||||
UseOrigin::CallCalleeOperand {
|
||||
caller_func_id: func_id,
|
||||
}
|
||||
} else {
|
||||
UseOrigin::IntraFuncOperandOrResultType {
|
||||
parent_func_id: func_id,
|
||||
}
|
||||
};
|
||||
ZombieUse {
|
||||
used_zombie_id: zombie.id,
|
||||
use_file_id_line_col: file_id_line_col,
|
||||
origin,
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
if !all_zombie_uses_in_func.is_empty() {
|
||||
self.id_to_zombie_kind
|
||||
.insert(func_id, ZombieKind::Uses(all_zombie_uses_in_func));
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
any
|
||||
}
|
||||
any
|
||||
}
|
||||
|
||||
// If an entry point references a zombie'd value, then the entry point would normally get removed.
|
||||
// That's an absolutely horrible experience to debug, though, so instead, create a nice error
|
||||
// message containing the stack trace of how the entry point got to the zombie value.
|
||||
fn report_error_zombies(
|
||||
sess: &Session,
|
||||
module: &Module,
|
||||
zombies: &FxHashMap<Word, Zombie>,
|
||||
) -> super::Result<()> {
|
||||
let mut span_regen = SpanRegenerator::new(sess.source_map(), module);
|
||||
struct ZombieReporter<'a> {
|
||||
sess: &'a Session,
|
||||
module: &'a Module,
|
||||
zombies: &'a Zombies,
|
||||
|
||||
let mut result = Ok(());
|
||||
let mut names = None;
|
||||
for root in super::dce::collect_roots(module) {
|
||||
if let Some(zombie) = zombies.get(&root) {
|
||||
let reason = span_regen.zombie_for_id(zombie.leaf_id).unwrap().reason;
|
||||
let span = span_regen
|
||||
.src_loc_for_id(zombie.leaf_id)
|
||||
.and_then(|src_loc| span_regen.src_loc_to_rustc(src_loc))
|
||||
.unwrap_or(DUMMY_SP);
|
||||
let names = names.get_or_insert_with(|| get_names(module));
|
||||
let stack = zombie
|
||||
.stack
|
||||
.iter()
|
||||
.map(|&s| get_name(names, s).into_owned());
|
||||
let stack_note = once("Stack:".to_string())
|
||||
.chain(stack)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
result = Err(sess.struct_span_err(span, reason).note(&stack_note).emit());
|
||||
id_to_name: Option<FxHashMap<Word, &'a str>>,
|
||||
span_regen: SpanRegenerator<'a>,
|
||||
}
|
||||
impl<'a> ZombieReporter<'a> {
|
||||
fn new(sess: &'a Session, module: &'a Module, zombies: &'a Zombies) -> Self {
|
||||
Self {
|
||||
sess,
|
||||
module,
|
||||
zombies,
|
||||
|
||||
id_to_name: None,
|
||||
span_regen: SpanRegenerator::new(sess.source_map(), module),
|
||||
}
|
||||
}
|
||||
result
|
||||
|
||||
// If an entry point references a zombie'd value, then the entry point would normally get removed.
|
||||
// That's an absolutely horrible experience to debug, though, so instead, create a nice error
|
||||
// message containing the stack trace of how the entry point got to the zombie value.
|
||||
fn report_all(mut self) -> super::Result<()> {
|
||||
let mut result = Ok(());
|
||||
// FIXME(eddyb) this loop means that every entry-point can potentially
|
||||
// list out all the leaves, but that shouldn't be a huge issue.
|
||||
for root_id in super::dce::collect_roots(self.module) {
|
||||
if let Some(zombie) = self.zombies.get_zombie_by_id(root_id) {
|
||||
for (_, mut err) in self.build_errors_keyed_by_leaf_id(zombie) {
|
||||
result = Err(err.emit());
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn add_use_note_to_err(
|
||||
&mut self,
|
||||
err: &mut DiagnosticBuilder<'a, ErrorGuaranteed>,
|
||||
span: Span,
|
||||
zombie: Zombie<'_>,
|
||||
zombie_use: &ZombieUse,
|
||||
) {
|
||||
let mut id_to_name = |id, kind| {
|
||||
self.id_to_name
|
||||
.get_or_insert_with(|| get_names(self.module))
|
||||
.get(&id)
|
||||
.map_or_else(
|
||||
|| format!("unnamed {kind} (%{id})"),
|
||||
|&name| format!("`{name}`"),
|
||||
)
|
||||
};
|
||||
let note = match zombie_use.origin {
|
||||
UseOrigin::GlobalOperandOrResultType => {
|
||||
format!("used by {}", id_to_name(zombie.id, "global"))
|
||||
}
|
||||
UseOrigin::IntraFuncOperandOrResultType { parent_func_id } => {
|
||||
format!(
|
||||
"used from within {}",
|
||||
id_to_name(parent_func_id, "function")
|
||||
)
|
||||
}
|
||||
UseOrigin::CallCalleeOperand { caller_func_id } => {
|
||||
format!("called by {}", id_to_name(caller_func_id, "function"))
|
||||
}
|
||||
};
|
||||
|
||||
let span = zombie_use
|
||||
.use_file_id_line_col
|
||||
.and_then(|(file_id, line, col)| {
|
||||
self.span_regen.src_loc_from_op_line(file_id, line, col)
|
||||
})
|
||||
.and_then(|src_loc| self.span_regen.src_loc_to_rustc(src_loc))
|
||||
.unwrap_or(span);
|
||||
err.span_note(span, note);
|
||||
}
|
||||
|
||||
fn build_errors_keyed_by_leaf_id(
|
||||
&mut self,
|
||||
zombie: Zombie<'_>,
|
||||
) -> FxIndexMap<Word, DiagnosticBuilder<'a, ErrorGuaranteed>> {
|
||||
// FIXME(eddyb) this is a bit inefficient, compared to some kind of
|
||||
// "small map", but this is the error path, and being correct is more
|
||||
// important here - in particular, we don't want to ignore *any* leaves.
|
||||
let mut errors_keyed_by_leaf_id = FxIndexMap::default();
|
||||
|
||||
let span = self
|
||||
.span_regen
|
||||
.src_loc_for_id(zombie.id)
|
||||
.and_then(|src_loc| self.span_regen.src_loc_to_rustc(src_loc))
|
||||
.unwrap_or(DUMMY_SP);
|
||||
match zombie.kind {
|
||||
ZombieKind::Leaf => {
|
||||
let reason = self.span_regen.zombie_for_id(zombie.id).unwrap().reason;
|
||||
errors_keyed_by_leaf_id.insert(zombie.id, self.sess.struct_span_err(span, reason));
|
||||
}
|
||||
ZombieKind::Uses(zombie_uses) => {
|
||||
for zombie_use in zombie_uses {
|
||||
let used_zombie = self
|
||||
.zombies
|
||||
.get_zombie_by_id(zombie_use.used_zombie_id)
|
||||
.unwrap();
|
||||
for (leaf_id, err) in self.build_errors_keyed_by_leaf_id(used_zombie) {
|
||||
use rustc_data_structures::fx::IndexEntry as Entry;
|
||||
match errors_keyed_by_leaf_id.entry(leaf_id) {
|
||||
Entry::Occupied(_) => err.cancel(),
|
||||
Entry::Vacant(entry) => {
|
||||
self.add_use_note_to_err(
|
||||
entry.insert(err),
|
||||
span,
|
||||
zombie,
|
||||
zombie_use,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
errors_keyed_by_leaf_id
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_zombies(
|
||||
@ -143,30 +313,35 @@ pub fn remove_zombies(
|
||||
opts: &super::Options,
|
||||
module: &mut Module,
|
||||
) -> super::Result<()> {
|
||||
let mut zombies = ZombieDecoration::decode_all(module)
|
||||
.map(|(id, _)| {
|
||||
(
|
||||
id,
|
||||
Zombie {
|
||||
leaf_id: id,
|
||||
stack: vec![],
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let mut zombies = Zombies {
|
||||
id_to_zombie_kind: ZombieDecoration::decode_all(module)
|
||||
.map(|(id, _)| (id, ZombieKind::Leaf))
|
||||
.collect(),
|
||||
};
|
||||
// Note: This is O(n^2).
|
||||
while spread_zombie(module, &mut zombies) {}
|
||||
while zombies.spread(module) {}
|
||||
|
||||
let result = report_error_zombies(sess, module, &zombies);
|
||||
let result = ZombieReporter::new(sess, module, &zombies).report_all();
|
||||
|
||||
// FIXME(eddyb) use `log`/`tracing` instead.
|
||||
if opts.print_all_zombie {
|
||||
let mut span_regen = SpanRegenerator::new(sess.source_map(), module);
|
||||
for (&zombie_id, zombie) in &zombies {
|
||||
let reason = span_regen.zombie_for_id(zombie.leaf_id).unwrap().reason;
|
||||
for &zombie_id in zombies.id_to_zombie_kind.keys() {
|
||||
let mut zombie_leaf_id = zombie_id;
|
||||
let mut infection_chain = SmallVec::<[_; 4]>::new();
|
||||
loop {
|
||||
zombie_leaf_id = match zombies.get_zombie_by_id(zombie_leaf_id).unwrap().kind {
|
||||
ZombieKind::Leaf => break,
|
||||
// FIXME(eddyb) this is all very lossy and should probably go away.
|
||||
ZombieKind::Uses(zombie_uses) => zombie_uses[0].used_zombie_id,
|
||||
};
|
||||
infection_chain.push(zombie_leaf_id);
|
||||
}
|
||||
|
||||
let reason = span_regen.zombie_for_id(zombie_leaf_id).unwrap().reason;
|
||||
eprint!("zombie'd %{zombie_id} because {reason}");
|
||||
if zombie_id != zombie.leaf_id {
|
||||
eprint!(" (infected by %{})", zombie.leaf_id);
|
||||
if !infection_chain.is_empty() {
|
||||
eprint!(" (infected via {:?})", infection_chain);
|
||||
}
|
||||
eprintln!();
|
||||
}
|
||||
@ -176,54 +351,46 @@ pub fn remove_zombies(
|
||||
let mut span_regen = SpanRegenerator::new(sess.source_map(), module);
|
||||
let names = get_names(module);
|
||||
for f in &module.functions {
|
||||
if let Some(zombie) = is_zombie(f.def.as_ref().unwrap(), &zombies) {
|
||||
if let Some(zombie) = zombies.get_zombie_by_id(f.def_id().unwrap()) {
|
||||
let mut zombie_leaf_id = zombie.id;
|
||||
loop {
|
||||
zombie_leaf_id = match zombies.get_zombie_by_id(zombie_leaf_id).unwrap().kind {
|
||||
ZombieKind::Leaf => break,
|
||||
// FIXME(eddyb) this is all very lossy and should probably go away.
|
||||
ZombieKind::Uses(zombie_uses) => zombie_uses[0].used_zombie_id,
|
||||
};
|
||||
}
|
||||
|
||||
let name = get_name(&names, f.def_id().unwrap());
|
||||
let reason = span_regen.zombie_for_id(zombie.leaf_id).unwrap().reason;
|
||||
let reason = span_regen.zombie_for_id(zombie_leaf_id).unwrap().reason;
|
||||
eprintln!("function removed {name:?} because {reason:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module
|
||||
.capabilities
|
||||
.retain(|inst| is_zombie(inst, &zombies).is_none());
|
||||
module
|
||||
.extensions
|
||||
.retain(|inst| is_zombie(inst, &zombies).is_none());
|
||||
module
|
||||
.ext_inst_imports
|
||||
.retain(|inst| is_zombie(inst, &zombies).is_none());
|
||||
if module
|
||||
.memory_model
|
||||
.as_ref()
|
||||
.map_or(false, |inst| is_zombie(inst, &zombies).is_some())
|
||||
// FIXME(eddyb) this should be unnecessary, either something is unused, and
|
||||
// it will get DCE'd *anyway*, or it caused an error.
|
||||
{
|
||||
module.memory_model = None;
|
||||
let keep = |inst: &Instruction| {
|
||||
if let Some(result_id) = inst.result_id {
|
||||
zombies.get_zombie_by_id(result_id).is_none()
|
||||
} else {
|
||||
zombies.zombies_used_from_inst(inst).next().is_none()
|
||||
}
|
||||
};
|
||||
module.capabilities.retain(keep);
|
||||
module.extensions.retain(keep);
|
||||
module.ext_inst_imports.retain(keep);
|
||||
module.memory_model = module.memory_model.take().filter(keep);
|
||||
module.entry_points.retain(keep);
|
||||
module.execution_modes.retain(keep);
|
||||
module.debug_string_source.retain(keep);
|
||||
module.debug_names.retain(keep);
|
||||
module.debug_module_processed.retain(keep);
|
||||
module.annotations.retain(keep);
|
||||
module.types_global_values.retain(keep);
|
||||
module.functions.retain(|f| keep(f.def.as_ref().unwrap()));
|
||||
}
|
||||
module
|
||||
.entry_points
|
||||
.retain(|inst| is_zombie(inst, &zombies).is_none());
|
||||
module
|
||||
.execution_modes
|
||||
.retain(|inst| is_zombie(inst, &zombies).is_none());
|
||||
module
|
||||
.debug_string_source
|
||||
.retain(|inst| is_zombie(inst, &zombies).is_none());
|
||||
module
|
||||
.debug_names
|
||||
.retain(|inst| is_zombie(inst, &zombies).is_none());
|
||||
module
|
||||
.debug_module_processed
|
||||
.retain(|inst| is_zombie(inst, &zombies).is_none());
|
||||
module
|
||||
.annotations
|
||||
.retain(|inst| is_zombie(inst, &zombies).is_none());
|
||||
module
|
||||
.types_global_values
|
||||
.retain(|inst| is_zombie(inst, &zombies).is_none());
|
||||
module
|
||||
.functions
|
||||
.retain(|f| is_zombie(f.def.as_ref().unwrap(), &zombies).is_none());
|
||||
|
||||
result
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ OpExtension "SPV_KHR_shader_clock"
|
||||
OpMemoryModel Logical Simple
|
||||
OpEntryPoint Fragment %1 "main"
|
||||
OpExecutionMode %1 OriginUpperLeft
|
||||
%2 = OpString "$OPSTRING_FILENAME/non-writable-storage_buffer.not_spirt.rs"
|
||||
%3 = OpString "$OPSTRING_FILENAME/cell.rs"
|
||||
%2 = OpString "$OPSTRING_FILENAME/cell.rs"
|
||||
%3 = OpString "$OPSTRING_FILENAME/non-writable-storage_buffer.not_spirt.rs"
|
||||
OpName %4 "buf_imm"
|
||||
OpName %5 "buf_mut"
|
||||
OpName %6 "buf_interior_mut"
|
||||
|
@ -4,11 +4,26 @@ error: Cannot memcpy dynamically sized data
|
||||
2460 | copy(src, dst, count)
|
||||
| ^
|
||||
|
|
||||
= note: Stack:
|
||||
core::intrinsics::copy::<f32>
|
||||
ptr_copy::copy_via_raw_ptr
|
||||
ptr_copy::main
|
||||
main
|
||||
note: used from within `core::intrinsics::copy::<f32>`
|
||||
--> $CORE_SRC/intrinsics.rs:2447:21
|
||||
|
|
||||
2447 | pub const unsafe fn copy<T>(src: *const T, dst: *mut T, count: usize) {
|
||||
| ^
|
||||
note: called by `ptr_copy::copy_via_raw_ptr`
|
||||
--> $DIR/ptr_copy.rs:30:18
|
||||
|
|
||||
30 | unsafe { core::ptr::copy(src, dst, 1) }
|
||||
| ^
|
||||
note: called by `ptr_copy::main`
|
||||
--> $DIR/ptr_copy.rs:35:5
|
||||
|
|
||||
35 | copy_via_raw_ptr(&i, o);
|
||||
| ^
|
||||
note: called by `main`
|
||||
--> $DIR/ptr_copy.rs:33:1
|
||||
|
|
||||
33 | #[spirv(fragment)]
|
||||
| ^
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
@ -4,9 +4,16 @@ error: constant arrays/structs cannot contain pointers to other constants
|
||||
20 | *pair_out = pair_deep_load(&(&123, &3.14));
|
||||
| ^
|
||||
|
|
||||
= note: Stack:
|
||||
nested_ref_in_composite::main_pair
|
||||
main_pair
|
||||
note: used from within `nested_ref_in_composite::main_pair`
|
||||
--> $DIR/nested-ref-in-composite.rs:20:17
|
||||
|
|
||||
20 | *pair_out = pair_deep_load(&(&123, &3.14));
|
||||
| ^
|
||||
note: called by `main_pair`
|
||||
--> $DIR/nested-ref-in-composite.rs:18:1
|
||||
|
|
||||
18 | #[spirv(fragment)]
|
||||
| ^
|
||||
|
||||
error: constant arrays/structs cannot contain pointers to other constants
|
||||
--> $DIR/nested-ref-in-composite.rs:25:19
|
||||
@ -14,9 +21,16 @@ error: constant arrays/structs cannot contain pointers to other constants
|
||||
25 | *array3_out = array3_deep_load(&[&0, &1, &2]);
|
||||
| ^
|
||||
|
|
||||
= note: Stack:
|
||||
nested_ref_in_composite::main_array3
|
||||
main_array3
|
||||
note: used from within `nested_ref_in_composite::main_array3`
|
||||
--> $DIR/nested-ref-in-composite.rs:25:19
|
||||
|
|
||||
25 | *array3_out = array3_deep_load(&[&0, &1, &2]);
|
||||
| ^
|
||||
note: called by `main_array3`
|
||||
--> $DIR/nested-ref-in-composite.rs:23:1
|
||||
|
|
||||
23 | #[spirv(fragment)]
|
||||
| ^
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
|
@ -1,8 +1,15 @@
|
||||
error: pointer has non-null integer address
|
||||
|
|
||||
= note: Stack:
|
||||
allocate_const_scalar::main
|
||||
main
|
||||
|
|
||||
note: used from within `allocate_const_scalar::main`
|
||||
--> $DIR/allocate_const_scalar.rs:15:20
|
||||
|
|
||||
15 | let _pointer = POINTER;
|
||||
| ^
|
||||
note: called by `main`
|
||||
--> $DIR/allocate_const_scalar.rs:13:1
|
||||
|
|
||||
13 | #[spirv(fragment)]
|
||||
| ^
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user