use crate::spirv_module::Instruction;
use crate::spirv_module::Module;
use crate::spirv_module::Operand;
use spirv::{Op, StorageClass, Word};
use std::collections::HashMap;
pub(super) fn rewrite_subword_packed_scalars(module: &mut Module) -> bool {
let mut ptr_info: HashMap<Word, (StorageClass, Word)> = HashMap::new();
let mut type_defs: HashMap<Word, Instruction> = HashMap::new();
for inst in &module.types_global_values {
if let Some(result) = inst.result_id {
type_defs.insert(result, inst.clone());
}
if inst.class.opcode == Op::TypePointer {
if let (Some(id), Some(Operand::StorageClass(s)), Some(Operand::IdRef(p))) =
(inst.result_id, inst.operands.first(), inst.operands.get(1))
{
ptr_info.insert(id, (*s, *p));
}
}
}
let mut cands: Vec<(Word, Word, u32)> = Vec::new(); for func in &module.functions {
for block in &func.blocks {
for inst in &block.instructions {
if inst.class.opcode != Op::Variable {
continue;
}
if inst.operands.len() != 1
|| inst.operands.first() != Some(&Operand::StorageClass(StorageClass::Function))
{
continue;
}
let (Some(var), Some(ptr_ty)) = (inst.result_id, inst.result_type) else {
continue;
};
let Some(&(StorageClass::Function, pointee)) = ptr_info.get(&ptr_ty) else {
continue;
};
if let Some(width) = scalar_int_width(&type_defs, pointee) {
cands.push((var, pointee, width));
}
}
}
}
if cands.is_empty() {
return false;
}
let mut next_id = module.header.as_ref().map(|h| h.bound).unwrap_or(0);
let mut changed = false;
for (var, scalar_ty, width) in cands {
if remodel_one(module, var, scalar_ty, width, &mut next_id) {
changed = true;
}
}
if changed {
if let Some(header) = module.header.as_mut() {
header.bound = next_id;
}
}
changed
}
fn scalar_int_width(defs: &HashMap<Word, Instruction>, ty: Word) -> Option<u32> {
let def = defs.get(&ty)?;
if def.class.opcode != Op::TypeInt {
return None;
}
match def.operands.first()? {
Operand::LiteralBit32(bits) => Some(*bits),
_ => None,
}
}
fn scalar_numeric_width(defs: &HashMap<Word, Instruction>, ty: Word) -> Option<u32> {
let def = defs.get(&ty)?;
match def.class.opcode {
Op::TypeInt | Op::TypeFloat => match def.operands.first()? {
Operand::LiteralBit32(bits) => Some(*bits),
_ => None,
},
_ => None,
}
}
struct Plan {
elem_ty: Word,
lanes: u32,
word_loads: Vec<Word>,
has_word_store: bool,
}
fn remodel_one(
module: &mut Module,
var: Word,
scalar_ty: Word,
width: u32,
next_id: &mut Word,
) -> bool {
let type_defs: HashMap<Word, Instruction> = module
.types_global_values
.iter()
.filter_map(|i| i.result_id.map(|id| (id, i.clone())))
.collect();
let ptr_info: HashMap<Word, (StorageClass, Word)> = module
.types_global_values
.iter()
.filter(|i| i.class.opcode == Op::TypePointer)
.filter_map(|i| {
let id = i.result_id?;
match (i.operands.first()?, i.operands.get(1)?) {
(Operand::StorageClass(s), Operand::IdRef(p)) => Some((id, (*s, *p))),
_ => None,
}
})
.collect();
let Some(plan) = validate(module, var, scalar_ty, width, &type_defs, &ptr_info) else {
return false;
};
let mut fresh = || {
let id = *next_id;
*next_id += 1;
id
};
let vec_ty = find_vector(module, plan.elem_ty, plan.lanes).unwrap_or_else(|| {
let id = fresh();
module.types_global_values.push(Instruction::new(
Op::TypeVector,
None,
Some(id),
vec![
Operand::IdRef(plan.elem_ty),
Operand::LiteralBit32(plan.lanes),
],
));
id
});
let vec_ptr = find_ptr(module, StorageClass::Function, vec_ty).unwrap_or_else(|| {
let id = fresh();
module.types_global_values.push(Instruction::new(
Op::TypePointer,
None,
Some(id),
vec![
Operand::StorageClass(StorageClass::Function),
Operand::IdRef(vec_ty),
],
));
id
});
let mut found = false;
for func in module.functions.iter_mut() {
for block in func.blocks.iter_mut() {
for inst in block.instructions.iter_mut() {
if inst.class.opcode == Op::Variable && inst.result_id == Some(var) {
inst.result_type = Some(vec_ptr);
found = true;
}
}
}
}
if !found {
return false;
}
rewrite_bodies(module, var, scalar_ty, vec_ty, &plan, next_id);
true
}
fn validate(
module: &Module,
var: Word,
scalar_ty: Word,
width: u32,
type_defs: &HashMap<Word, Instruction>,
ptr_info: &HashMap<Word, (StorageClass, Word)>,
) -> Option<Plan> {
let mut elem_ty: Option<Word> = None;
let mut word_loads: Vec<Word> = Vec::new();
let mut has_word_store = false;
let mut saw_subword_chain = false;
for func in &module.functions {
for block in &func.blocks {
for inst in &block.instructions {
for (oi, op) in inst.operands.iter().enumerate() {
if !matches!(op, Operand::IdRef(id) if *id == var) {
continue;
}
match inst.class.opcode {
Op::InBoundsAccessChain | Op::AccessChain if oi == 0 => {
if inst.operands.len() != 2 {
return None;
}
let result_ty = inst.result_type?;
let &(sc, pointee) = ptr_info.get(&result_ty)?;
if sc != StorageClass::Function {
return None;
}
let ew = scalar_numeric_width(type_defs, pointee)?;
if ew >= width || !width.is_multiple_of(ew) {
return None;
}
match elem_ty {
Some(t) if t != pointee => return None,
_ => elem_ty = Some(pointee),
}
saw_subword_chain = true;
}
Op::Load if oi == 0 => {
if inst.result_type != Some(scalar_ty) {
return None;
}
word_loads.push(inst.result_id?);
}
Op::Store if oi == 0 => {
has_word_store = true;
}
_ => return None,
}
}
}
}
}
let elem_ty = elem_ty?;
if !saw_subword_chain {
return None;
}
let ew = scalar_numeric_width(type_defs, elem_ty)?;
let lanes = width / ew;
if !(2..=4).contains(&lanes) {
return None;
}
Some(Plan {
elem_ty,
lanes,
word_loads,
has_word_store,
})
}
fn rewrite_bodies(
module: &mut Module,
var: Word,
scalar_ty: Word,
vec_ty: Word,
plan: &Plan,
next_id: &mut Word,
) {
for func in module.functions.iter_mut() {
for block in func.blocks.iter_mut() {
let insts = std::mem::take(&mut block.instructions);
let mut out = Vec::with_capacity(insts.len());
for inst in insts {
if inst.class.opcode == Op::Load
&& inst
.result_id
.map(|r| plan.word_loads.contains(&r))
.unwrap_or(false)
&& operand_id(&inst, 0) == Some(var)
{
let rid = inst.result_id.unwrap();
let tmp = *next_id;
*next_id += 1;
out.push(Instruction::new(
Op::Load,
Some(vec_ty),
Some(tmp),
vec![Operand::IdRef(var)],
));
out.push(Instruction::new(
Op::Bitcast,
Some(scalar_ty),
Some(rid),
vec![Operand::IdRef(tmp)],
));
continue;
}
if plan.has_word_store
&& inst.class.opcode == Op::Store
&& operand_id(&inst, 0) == Some(var)
{
let val = operand_id(&inst, 1).unwrap();
let tmp = *next_id;
*next_id += 1;
out.push(Instruction::new(
Op::Bitcast,
Some(vec_ty),
Some(tmp),
vec![Operand::IdRef(val)],
));
out.push(Instruction::new(
Op::Store,
None,
None,
vec![Operand::IdRef(var), Operand::IdRef(tmp)],
));
continue;
}
out.push(inst);
}
block.instructions = out;
}
}
}
fn find_vector(module: &Module, component: Word, count: u32) -> Option<Word> {
module.types_global_values.iter().find_map(|i| {
(i.class.opcode == Op::TypeVector
&& i.operands.first() == Some(&Operand::IdRef(component))
&& i.operands.get(1) == Some(&Operand::LiteralBit32(count)))
.then_some(i.result_id)
.flatten()
})
}
fn find_ptr(module: &Module, sc: StorageClass, pointee: Word) -> Option<Word> {
module.types_global_values.iter().find_map(|i| {
(i.class.opcode == Op::TypePointer
&& i.operands.first() == Some(&Operand::StorageClass(sc))
&& i.operands.get(1) == Some(&Operand::IdRef(pointee)))
.then_some(i.result_id)
.flatten()
})
}
fn operand_id(inst: &Instruction, idx: usize) -> Option<Word> {
match inst.operands.get(idx) {
Some(Operand::IdRef(id)) => Some(*id),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::spirv_module::{Block, Function, ModuleHeader};
fn i(op: Op, ty: Option<Word>, res: Option<Word>, ops: Vec<Operand>) -> Instruction {
Instruction::new(op, ty, res, ops)
}
#[test]
fn half2_packed_into_uint_remodels_to_vector() {
let mut m = Module::new();
m.header = Some(ModuleHeader::new(40));
m.types_global_values = vec![
i(
Op::TypeInt,
None,
Some(1),
vec![Operand::LiteralBit32(32), Operand::LiteralBit32(0)],
),
i(
Op::TypeFloat,
None,
Some(2),
vec![Operand::LiteralBit32(16)],
),
i(
Op::TypePointer,
None,
Some(4),
vec![
Operand::StorageClass(StorageClass::Function),
Operand::IdRef(1),
],
),
i(
Op::TypePointer,
None,
Some(5),
vec![
Operand::StorageClass(StorageClass::Function),
Operand::IdRef(2),
],
),
i(
Op::Constant,
Some(1),
Some(10),
vec![Operand::LiteralBit32(0)],
),
i(
Op::Constant,
Some(1),
Some(11),
vec![Operand::LiteralBit32(1)],
),
];
let mut block = Block::new();
block.label = Some(i(Op::Label, None, Some(30), vec![]));
block.instructions = vec![
i(
Op::Variable,
Some(4),
Some(20),
vec![Operand::StorageClass(StorageClass::Function)],
),
i(
Op::InBoundsAccessChain,
Some(5),
Some(31),
vec![Operand::IdRef(20), Operand::IdRef(10)],
),
i(
Op::Store,
None,
None,
vec![Operand::IdRef(31), Operand::IdRef(33)],
),
i(
Op::InBoundsAccessChain,
Some(5),
Some(32),
vec![Operand::IdRef(20), Operand::IdRef(11)],
),
i(
Op::Store,
None,
None,
vec![Operand::IdRef(32), Operand::IdRef(34)],
),
i(Op::Load, Some(1), Some(35), vec![Operand::IdRef(20)]),
i(Op::Return, None, None, vec![]),
];
let mut func = Function::new();
func.blocks = vec![block];
m.functions = vec![func];
assert!(rewrite_subword_packed_scalars(&mut m));
let var = m.functions[0].blocks[0]
.instructions
.iter()
.find(|x| x.result_id == Some(20) && x.class.opcode == Op::Variable)
.unwrap();
let var_ptr = var.result_type.unwrap();
assert_ne!(var_ptr, 4);
let pointee = m
.types_global_values
.iter()
.find(|x| x.result_id == Some(var_ptr))
.and_then(|x| x.operands.get(1))
.cloned();
let vec_id = match pointee {
Some(Operand::IdRef(v)) => v,
_ => panic!("var ptr has no pointee"),
};
let vecdef = m
.types_global_values
.iter()
.find(|x| x.result_id == Some(vec_id))
.unwrap();
assert_eq!(vecdef.class.opcode, Op::TypeVector);
assert_eq!(vecdef.operands.first(), Some(&Operand::IdRef(2))); assert_eq!(vecdef.operands.get(1), Some(&Operand::LiteralBit32(2)));
let body = &m.functions[0].blocks[0].instructions;
let bc = body.iter().find(|x| x.result_id == Some(35)).unwrap();
assert_eq!(bc.class.opcode, Op::Bitcast);
assert_eq!(bc.result_type, Some(1)); let src = match bc.operands.first() {
Some(Operand::IdRef(s)) => *s,
_ => panic!("bitcast has no source"),
};
let vload = body.iter().find(|x| x.result_id == Some(src)).unwrap();
assert_eq!(vload.class.opcode, Op::Load);
assert_eq!(vload.result_type, Some(vec_id));
assert_eq!(vload.operands.first(), Some(&Operand::IdRef(20)));
let g0 = body.iter().find(|x| x.result_id == Some(31)).unwrap();
assert_eq!(g0.class.opcode, Op::InBoundsAccessChain);
assert_eq!(g0.result_type, Some(5));
}
#[test]
fn uint_alloca_with_foreign_use_is_left_untouched() {
let mut m = Module::new();
m.header = Some(ModuleHeader::new(40));
m.types_global_values = vec![
i(
Op::TypeInt,
None,
Some(1),
vec![Operand::LiteralBit32(32), Operand::LiteralBit32(0)],
),
i(
Op::TypeFloat,
None,
Some(2),
vec![Operand::LiteralBit32(16)],
),
i(
Op::TypePointer,
None,
Some(4),
vec![
Operand::StorageClass(StorageClass::Function),
Operand::IdRef(1),
],
),
i(
Op::TypePointer,
None,
Some(5),
vec![
Operand::StorageClass(StorageClass::Function),
Operand::IdRef(2),
],
),
i(
Op::Constant,
Some(1),
Some(10),
vec![Operand::LiteralBit32(0)],
),
];
let mut block = Block::new();
block.label = Some(i(Op::Label, None, Some(30), vec![]));
block.instructions = vec![
i(
Op::Variable,
Some(4),
Some(20),
vec![Operand::StorageClass(StorageClass::Function)],
),
i(
Op::InBoundsAccessChain,
Some(5),
Some(31),
vec![Operand::IdRef(20), Operand::IdRef(10)],
),
i(
Op::FunctionCall,
Some(1),
Some(32),
vec![Operand::IdRef(99), Operand::IdRef(20)],
),
i(Op::Return, None, None, vec![]),
];
let mut func = Function::new();
func.blocks = vec![block];
m.functions = vec![func];
assert!(!rewrite_subword_packed_scalars(&mut m));
}
}