linker/zombies: report all reachable zombies, w/ OpLine-based stack traces.

This commit is contained in:
Eduard-Mihai Burtescu 2023-04-19 04:19:16 +03:00 committed by Eduard-Mihai Burtescu
parent 3bbfaf5221
commit 633dff18bd
7 changed files with 493 additions and 245 deletions

View File

@ -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);
}

View File

@ -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() {

View File

@ -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
}

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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