1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
//! Passes that pertain to `OpEntryPoint`'s "interface variables".
use crate::linker::ipo::CallGraph;
use indexmap::{IndexMap, IndexSet};
use rspirv::dr::{Module, Operand};
use rspirv::spirv::{Op, StorageClass, Word};
use std::mem;
type Id = Word;
/// Update `OpEntryPoint`s to contain all of the `OpVariable`s they reference,
/// whether directly or through some function in their call graph.
///
/// This is needed for (arguably-not-interface) `Private` in SPIR-V >= 1.4,
/// but also any interface variables declared "out of band" (e.g. via `asm!`).
pub fn gather_all_interface_vars_from_uses(module: &mut Module) {
// Start by mapping out which global (i.e. `OpVariable` or constants) IDs
// can be used to access any interface-relevant `OpVariable`s
// (where "interface-relevant" depends on the version, see comments below).
let mut used_vars_per_global_id: IndexMap<Id, IndexSet<Id>> = IndexMap::new();
let version = module.header.as_ref().unwrap().version();
for inst in &module.types_global_values {
let mut used_vars = IndexSet::new();
// Base case: the global itself is an interface-relevant `OpVariable`.
let interface_relevant_var = inst.class.opcode == Op::Variable && {
if version > (1, 3) {
// SPIR-V >= v1.4 includes all OpVariables in the interface.
true
} else {
let storage_class = inst.operands[0].unwrap_storage_class();
// SPIR-V <= v1.3 only includes Input and Output in the interface.
storage_class == StorageClass::Input || storage_class == StorageClass::Output
}
};
if interface_relevant_var {
used_vars.insert(inst.result_id.unwrap());
}
// Nested constant refs (e.g. `&&&0`) can create chains of `OpVariable`s
// where only the outer-most `OpVariable` may be accessed directly,
// but the interface variables need to include all the nesting levels.
used_vars.extend(
inst.operands
.iter()
.filter_map(|operand| operand.id_ref_any())
.filter_map(|id| used_vars_per_global_id.get(&id))
.flatten(),
);
if !used_vars.is_empty() {
used_vars_per_global_id.insert(inst.result_id.unwrap(), used_vars);
}
}
// Initial uses come from functions directly referencing global instructions.
let mut used_vars_per_fn_idx: Vec<IndexSet<Id>> = module
.functions
.iter()
.map(|func| {
func.all_inst_iter()
.flat_map(|inst| &inst.operands)
.filter_map(|operand| operand.id_ref_any())
.filter_map(|id| used_vars_per_global_id.get(&id))
.flatten()
.copied()
.collect()
})
.collect();
// Uses can then be propagated through the call graph, from callee to caller.
let call_graph = CallGraph::collect(module);
for caller_idx in call_graph.post_order() {
let mut used_vars = mem::take(&mut used_vars_per_fn_idx[caller_idx]);
for &callee_idx in &call_graph.callees[caller_idx] {
used_vars.extend(&used_vars_per_fn_idx[callee_idx]);
}
used_vars_per_fn_idx[caller_idx] = used_vars;
}
// All transitive uses are available, add them to `OpEntryPoint`s.
for (i, entry) in module.entry_points.iter_mut().enumerate() {
assert_eq!(entry.class.opcode, Op::EntryPoint);
let &entry_func_idx = call_graph.entry_points.get_index(i).unwrap();
assert_eq!(
module.functions[entry_func_idx].def_id().unwrap(),
entry.operands[1].unwrap_id_ref()
);
// NOTE(eddyb) it might be better to remove any unused vars, or warn
// the user about their presence, but for now this keeps them around.
let mut interface_vars: IndexSet<Id> = entry.operands[3..]
.iter()
.map(|operand| operand.unwrap_id_ref())
.collect();
interface_vars.extend(&used_vars_per_fn_idx[entry_func_idx]);
entry.operands.truncate(3);
entry
.operands
.extend(interface_vars.iter().map(|&id| Operand::IdRef(id)));
}
}