use std::collections::HashMap;
use crate::spirv_module::Instruction;
use crate::spirv_module::Operand;
use spirv::{Op, StorageClass, Word};
use super::super::{air_names, Ctx};
pub(crate) fn materialize_texture_array_loads(ctx: &mut Ctx, entry_idx: usize) {
if ctx.image_array_vars.is_empty() {
return;
}
let names = air_names(&ctx.module);
let mut defs: HashMap<Word, Instruction> = HashMap::new();
for blk in &ctx.module.functions[entry_idx].blocks {
for inst in &blk.instructions {
if let Some(rid) = inst.result_id {
defs.insert(rid, inst.clone());
}
}
}
let mut candidates: Vec<(Word, Word)> = Vec::new();
let mut seen: std::collections::HashSet<Word> = std::collections::HashSet::new();
for blk in &ctx.module.functions[entry_idx].blocks {
for inst in &blk.instructions {
if inst.class.opcode != Op::FunctionCall {
continue;
}
let Some(Operand::IdRef(callee)) = inst.operands.first() else {
continue;
};
let Some(name) = names.get(callee) else {
continue;
};
if !is_texture_intrinsic(name) {
continue;
}
let Some(Operand::IdRef(handle)) = inst.operands.get(1) else {
continue;
};
if !seen.insert(*handle) {
continue;
}
let Some(load) = defs.get(handle) else {
continue;
};
if load.class.opcode != Op::Load {
continue;
}
let Some(Operand::IdRef(ptr)) = load.operands.first() else {
continue;
};
candidates.push((*handle, *ptr));
}
}
let mut handles: HashMap<Word, (Word, Word)> = HashMap::new();
for (handle, ptr) in candidates {
if let Some((arrayvar, idx)) = resolve_array_element(ctx, &defs, ptr) {
handles.insert(handle, (arrayvar, idx));
}
}
if handles.is_empty() {
return;
}
let mut new_access_chains: HashMap<Word, Instruction> = HashMap::new(); let mut retyped_load_ptr: HashMap<Word, (Word, Word)> = HashMap::new(); let mut dead_roots: Vec<Word> = Vec::new();
for (&handle, &(arrayvar, idx)) in &handles {
let &(elem_image_ty, dim, comp) = ctx.image_array_vars.get(&arrayvar).unwrap();
let ptr_image = ctx.ty_ptr(StorageClass::UniformConstant, elem_image_ty);
let p = ctx.module.fresh_id();
new_access_chains.insert(
handle,
Instruction::new(
Op::AccessChain,
Some(ptr_image),
Some(p),
vec![Operand::IdRef(arrayvar), Operand::IdRef(idx)],
),
);
retyped_load_ptr.insert(handle, (elem_image_ty, p));
ctx.image_dims.insert(handle, dim);
ctx.image_comp.insert(handle, comp);
if let Some(load) = defs.get(&handle) {
if let Some(Operand::IdRef(old_ptr)) = load.operands.first() {
if *old_ptr != arrayvar {
dead_roots.push(*old_ptr);
}
}
}
}
let func = &mut ctx.module.functions[entry_idx];
for blk in &mut func.blocks {
let mut rebuilt: Vec<Instruction> =
Vec::with_capacity(blk.instructions.len() + handles.len());
for inst in blk.instructions.drain(..) {
if let Some(handle) = inst.result_id {
if let (Some(chain), Some(&(image_ty, p))) = (
new_access_chains.get(&handle),
retyped_load_ptr.get(&handle),
) {
rebuilt.push(chain.clone());
let mut load = inst;
load.result_type = Some(image_ty);
load.operands = vec![Operand::IdRef(p)];
rebuilt.push(load);
continue;
}
}
rebuilt.push(inst);
}
blk.instructions = rebuilt;
}
sweep_dead_pointer_derivations(ctx, entry_idx, &dead_roots);
}
fn is_texture_intrinsic(name: &str) -> bool {
name.starts_with("air.sample_texture")
|| name.starts_with("air.sample_depth")
|| name.starts_with("air.read_texture")
|| name.starts_with("air.read_depth")
|| name.starts_with("air.gather_texture")
|| name.starts_with("air.gather_depth")
|| name.starts_with("air.get_width_texture")
|| name.starts_with("air.get_height_texture")
|| name.starts_with("air.get_depth_texture")
|| name.starts_with("air.get_array_size_texture")
|| name.starts_with("air.get_width_depth")
|| name.starts_with("air.get_height_depth")
|| name.starts_with("air.get_depth_depth")
|| name.starts_with("air.get_num_mip_levels_texture")
|| name.starts_with("air.get_num_samples_texture")
|| name.starts_with("air.is_null_texture")
}
fn resolve_array_element(
ctx: &mut Ctx,
defs: &HashMap<Word, Instruction>,
ptr: Word,
) -> Option<(Word, Word)> {
if ctx.image_array_vars.contains_key(&ptr) {
let zero = ctx.const_uint(0);
return Some((ptr, zero));
}
let inst = defs.get(&ptr)?;
match inst.class.opcode {
Op::AccessChain | Op::InBoundsAccessChain | Op::PtrAccessChain => {
let Operand::IdRef(base) = inst.operands.first()? else {
return None;
};
let arrayvar = resolve_ptr_root(ctx, defs, *base)?;
let Operand::IdRef(idx) = inst.operands.get(1)? else {
return None;
};
Some((arrayvar, *idx))
}
Op::Bitcast | Op::CopyObject => {
let Operand::IdRef(src) = inst.operands.first()? else {
return None;
};
resolve_array_element(ctx, defs, *src)
}
_ => None,
}
}
fn resolve_ptr_root(ctx: &Ctx, defs: &HashMap<Word, Instruction>, mut ptr: Word) -> Option<Word> {
for _ in 0..8 {
if ctx.image_array_vars.contains_key(&ptr) {
return Some(ptr);
}
let inst = defs.get(&ptr)?;
match inst.class.opcode {
Op::Bitcast | Op::CopyObject => {
let Operand::IdRef(src) = inst.operands.first()? else {
return None;
};
ptr = *src;
}
_ => return None,
}
}
None
}
fn sweep_dead_pointer_derivations(ctx: &mut Ctx, entry_idx: usize, roots: &[Word]) {
let mut candidates: Vec<Word> = roots.to_vec();
loop {
let mut used: std::collections::HashSet<Word> = std::collections::HashSet::new();
for f in &ctx.module.functions {
for blk in &f.blocks {
for inst in &blk.instructions {
for op in &inst.operands {
if let Operand::IdRef(id) = op {
used.insert(*id);
}
}
}
}
}
for inst in ctx
.module
.types_global_values
.iter()
.chain(ctx.new_globals.iter())
.chain(ctx.module.debug_names.iter())
.chain(ctx.module.annotations.iter())
.chain(ctx.module.entry_points.iter())
{
for op in &inst.operands {
if let Operand::IdRef(id) = op {
used.insert(*id);
}
}
}
let func = &mut ctx.module.functions[entry_idx];
let mut removed_any = false;
let mut freed_sources: Vec<Word> = Vec::new();
for blk in &mut func.blocks {
blk.instructions.retain(|inst| {
let Some(rid) = inst.result_id else {
return true;
};
let is_pointer_deriv = matches!(
inst.class.opcode,
Op::AccessChain
| Op::InBoundsAccessChain
| Op::PtrAccessChain
| Op::Bitcast
| Op::CopyObject
);
if is_pointer_deriv && candidates.contains(&rid) && !used.contains(&rid) {
for op in &inst.operands {
if let Operand::IdRef(id) = op {
freed_sources.push(*id);
}
}
removed_any = true;
return false;
}
true
});
}
if !removed_any {
break;
}
candidates.extend(freed_sources);
}
}