Poison reason tracking

This commit is contained in:
khyperia 2020-09-14 10:06:37 +02:00
parent 31ac70ea07
commit ede5a1ab7f
3 changed files with 86 additions and 53 deletions

View File

@ -35,7 +35,6 @@ use rustc_target::abi::{
use rustc_target::spec::{HasTargetSpec, Target};
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::HashSet;
use std::iter::once;
use std::ops::Range;
@ -68,7 +67,7 @@ pub struct CodegenCx<'tcx> {
pub vtables: RefCell<FxHashMap<(Ty<'tcx>, Option<PolyExistentialTraitRef<'tcx>>), SpirvValue>>,
pub ext_inst: RefCell<ExtInst>,
/// Invalid spir-v IDs that should be stripped from the final binary
poisoned_values: RefCell<HashSet<Word>>,
poisoned_values: RefCell<HashMap<Word, &'static str>>,
pub kernel_mode: bool,
}
@ -135,8 +134,8 @@ impl<'tcx> CodegenCx<'tcx> {
self.lookup_type(ty).debug(ty, self)
}
pub fn poison(&self, word: Word) {
self.poisoned_values.borrow_mut().insert(word);
pub fn poison(&self, word: Word, reason: &'static str) {
self.poisoned_values.borrow_mut().insert(word, reason);
}
pub fn finalize_module(self) -> Module {
@ -213,7 +212,7 @@ impl<'tcx> CodegenCx<'tcx> {
.with_type(ty),
SpirvType::Integer(128, _) => {
let result = self.emit_global().undef(ty, None);
self.poison(result);
self.poison(result, "u128 constant");
result.with_type(ty)
}
other => panic!("constant_int invalid on type {}", other.debug(ty, self)),
@ -524,7 +523,7 @@ impl<'tcx> StaticMethods for CodegenCx<'tcx> {
.variable(ty, None, StorageClass::Function, Some(cv.def))
.with_type(ty);
// TODO: These should be StorageClass::UniformConstant, so just poison for now.
self.poison(result.def);
self.poison(result.def, "static_addr_of");
result
}
@ -618,7 +617,7 @@ fn declare_fn<'tcx>(
if crate::is_blocklisted_fn(name) {
// This can happen if we call a blocklisted function in another crate.
let result = emit.undef(function_type, None);
cx.poison(result);
cx.poison(result, "called blocklisted fn");
return result.with_type(function_type);
}
let fn_id = emit
@ -758,7 +757,7 @@ impl<'tcx> DeclareMethods<'tcx> for CodegenCx<'tcx> {
.variable(ptr_ty, None, StorageClass::Function, None)
.with_type(ptr_ty);
// TODO: These should be StorageClass::Private, so just poison for now.
self.poison(result.def);
self.poison(result.def, "declare_global");
self.declared_values
.borrow_mut()
.insert(name.to_string(), result);
@ -909,7 +908,7 @@ impl<'tcx> ConstMethods<'tcx> for CodegenCx<'tcx> {
.variable(ty, None, StorageClass::Function, Some(raw_bytes.def))
.with_type(ty);
// The types don't line up (dynamic array vs. constant array)
self.poison(result.def);
self.poison(result.def, "constant string");
result
});
// let cs = consts::ptrcast(
@ -1040,7 +1039,7 @@ impl<'tcx> ConstMethods<'tcx> for CodegenCx<'tcx> {
) => {
if a_space != b_space {
// TODO: Emit the correct type that is passed into this function.
self.poison(value.def);
self.poison(value.def, "invalid pointer space in constant");
}
assert_ty_eq!(self, a, b);
}
@ -1070,7 +1069,7 @@ impl<'tcx> ConstMethods<'tcx> for CodegenCx<'tcx> {
} else {
// constant ptrcast is not supported in spir-v
let result = val.def.with_type(ty);
self.poison(result.def);
self.poison(result.def, "const_ptrcast");
result
}
}
@ -1190,7 +1189,7 @@ fn create_const_alloc2(
*data = *c + asdf->y[*c];
}
*/
cx.poison(result.def);
cx.poison(result.def, "constant runtime array value");
result
}
SpirvType::Pointer { .. } => {

View File

@ -1,34 +1,42 @@
use rspirv::dr::{Block, Function, Instruction, Module, Operand};
use rspirv::spirv::{Op, Word};
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::iter::once;
use std::mem::replace;
fn contains_poison(inst: &Instruction, poison: &HashSet<Word>) -> bool {
inst.result_type.map_or(false, |w| poison.contains(&w))
|| inst.operands.iter().any(|op| match op {
rspirv::dr::Operand::IdMemorySemantics(w)
| rspirv::dr::Operand::IdScope(w)
| rspirv::dr::Operand::IdRef(w) => poison.contains(w),
_ => false,
})
fn contains_poison(
inst: &Instruction,
poison: &HashMap<Word, &'static str>,
) -> Option<&'static str> {
inst.result_type.map_or_else(
|| {
inst.operands.iter().find_map(|op| match op {
rspirv::dr::Operand::IdMemorySemantics(w)
| rspirv::dr::Operand::IdScope(w)
| rspirv::dr::Operand::IdRef(w) => poison.get(w).copied(),
_ => None,
})
},
|w| poison.get(&w).copied(),
)
}
fn is_poison(inst: &Instruction, poison: &HashSet<Word>) -> bool {
fn is_poison(inst: &Instruction, poison: &HashMap<Word, &'static str>) -> Option<&'static str> {
if let Some(result_id) = inst.result_id {
poison.contains(&result_id)
poison.get(&result_id).copied()
} else {
contains_poison(inst, poison)
}
}
pub fn poison_pass(module: &mut Module, poison: &mut HashSet<Word>) {
pub fn poison_pass(module: &mut Module, poison: &mut HashMap<Word, &'static str>) {
// Note: This is O(n^2).
while spread_poison(module, poison) {}
if option_env!("PRINT_POISON").is_some() {
for f in &module.functions {
if is_poison(f.def.as_ref().unwrap(), poison) {
if let Some(reason) = is_poison(f.def.as_ref().unwrap(), poison) {
let name_id = f.def.as_ref().unwrap().result_id.unwrap();
let name = module.debugs.iter().find(|inst| {
inst.class.opcode == Op::Name && inst.operands[0] == Operand::IdRef(name_id)
@ -40,61 +48,81 @@ pub fn poison_pass(module: &mut Module, poison: &mut HashSet<Word>) {
},
_ => format!("{}", name_id),
};
println!("Function removed: {}", name)
println!("Function removed {:?} because {:?}", name, reason)
}
}
}
module.capabilities.retain(|inst| !is_poison(inst, poison));
module.extensions.retain(|inst| !is_poison(inst, poison));
module
.capabilities
.retain(|inst| is_poison(inst, poison).is_none());
module
.extensions
.retain(|inst| is_poison(inst, poison).is_none());
module
.ext_inst_imports
.retain(|inst| !is_poison(inst, poison));
.retain(|inst| is_poison(inst, poison).is_none());
if module
.memory_model
.as_ref()
.map_or(false, |inst| is_poison(inst, poison))
.map_or(false, |inst| is_poison(inst, poison).is_some())
{
module.memory_model = None;
}
module.entry_points.retain(|inst| !is_poison(inst, poison));
module
.entry_points
.retain(|inst| is_poison(inst, poison).is_none());
module
.execution_modes
.retain(|inst| !is_poison(inst, poison));
module.debugs.retain(|inst| !is_poison(inst, poison));
module.annotations.retain(|inst| !is_poison(inst, poison));
.retain(|inst| is_poison(inst, poison).is_none());
module
.debugs
.retain(|inst| is_poison(inst, poison).is_none());
module
.annotations
.retain(|inst| is_poison(inst, poison).is_none());
module
.types_global_values
.retain(|inst| !is_poison(inst, poison));
.retain(|inst| is_poison(inst, poison).is_none());
module
.functions
.retain(|f| !is_poison(f.def.as_ref().unwrap(), poison));
.retain(|f| is_poison(f.def.as_ref().unwrap(), poison).is_none());
}
fn spread_poison(module: &mut Module, poison: &mut HashSet<Word>) -> bool {
fn spread_poison(module: &mut Module, poison: &mut HashMap<Word, &'static str>) -> bool {
let mut any = false;
// globals are easy
for inst in module.global_inst_iter() {
if let Some(result_id) = inst.result_id {
if contains_poison(inst, poison) && poison.insert(result_id) {
any = true;
if let Some(reason) = contains_poison(inst, poison) {
match poison.entry(result_id) {
Entry::Vacant(entry) => {
entry.insert(reason);
any = true;
}
Entry::Occupied(_) => {}
}
}
}
}
// function IDs implicitly reference their contents
for func in &module.functions {
let mut func_poisoned = false;
let mut func_poisoned = None;
let mut spread_func = |inst: &Instruction| {
if let Some(result_id) = inst.result_id {
if contains_poison(inst, poison) {
if poison.insert(result_id) {
any = true;
if let Some(reason) = contains_poison(inst, poison) {
match poison.entry(result_id) {
Entry::Vacant(entry) => {
entry.insert(reason);
any = true;
}
Entry::Occupied(_) => {}
}
func_poisoned = true;
} else if poison.contains(&result_id) {
func_poisoned = true;
func_poisoned = Some(func_poisoned.unwrap_or(reason));
} else if let Some(reason) = poison.get(&result_id) {
func_poisoned = Some(func_poisoned.unwrap_or(reason));
}
} else if is_poison(inst, poison) {
func_poisoned = true;
} else if let Some(reason) = is_poison(inst, poison) {
func_poisoned = Some(func_poisoned.unwrap_or(reason));
}
};
for def in &func.def {
@ -114,8 +142,14 @@ fn spread_poison(module: &mut Module, poison: &mut HashSet<Word>) -> bool {
for inst in &func.end {
spread_func(inst);
}
if func_poisoned && poison.insert(func.def.as_ref().unwrap().result_id.unwrap()) {
any = true;
if let Some(reason) = func_poisoned {
match poison.entry(func.def.as_ref().unwrap().result_id.unwrap()) {
Entry::Vacant(entry) => {
entry.insert(reason);
any = true;
}
Entry::Occupied(_) => {}
}
}
}
any

View File

@ -118,7 +118,7 @@ impl SpirvType {
.type_int(width, if signedness { 1 } else { 0 });
match width {
8 | 16 | 32 | 64 => (),
128 => cx.poison(result),
128 => cx.poison(result, "u128"),
other => panic!("Integer width {} invalid for spir-v", other),
};
result
@ -187,7 +187,7 @@ impl SpirvType {
SpirvType::RuntimeArray { element } => {
let result = cx.emit_global().type_runtime_array(element);
if cx.kernel_mode {
cx.poison(result);
cx.poison(result, "RuntimeArray in kernel mode");
}
result
}
@ -198,7 +198,7 @@ impl SpirvType {
let result = cx.emit_global().type_pointer(None, storage_class, pointee);
// no pointers to functions
if let SpirvType::Function { .. } = cx.lookup_type(pointee) {
cx.poison(result)
cx.poison(result, "pointer to function")
}
result
}
@ -228,7 +228,7 @@ impl SpirvType {
.type_pointer(Some(id), storage_class, pointee);
// no pointers to functions
if let SpirvType::Function { .. } = cx.lookup_type(pointee) {
cx.poison(result)
cx.poison(result, "pointer to function")
}
result
}