use super::*;
use crate::passes::stage_input::{layout_ty_size_align, round_up};
pub(in crate::passes) fn const_int_like(ctx: &mut Ctx, like: Word, value: u64) -> Word {
let Some(ty) = value_result_type(ctx, like) else {
return ctx.const_uint(value as u32);
};
let Some(def) = type_def_of(ctx, ty) else {
return ctx.const_uint(value as u32);
};
if def.class.opcode != Op::TypeInt {
return ctx.const_uint(value as u32);
}
match def.operands.first() {
Some(Operand::LiteralBit32(64)) => {
let id = ctx.module.fresh_id();
ctx.new_globals.push(Instruction::new(
Op::Constant,
Some(ty),
Some(id),
vec![Operand::LiteralBit64(value)],
));
id
}
_ => ctx.const_uint(value as u32),
}
}
pub(in crate::passes) fn narrow_access_chain_indices(ctx: &mut Ctx, entry_idx: usize) {
let n_blocks = ctx.module.functions[entry_idx].blocks.len();
for bi in 0..n_blocks {
let mut new_insts: Vec<Instruction> = vec![];
let insts = ctx.module.functions[entry_idx].blocks[bi]
.instructions
.clone();
for mut inst in insts {
if !matches!(
inst.class.opcode,
Op::AccessChain | Op::InBoundsAccessChain | Op::PtrAccessChain
) {
new_insts.push(inst);
continue;
}
let pre: Vec<Instruction> = vec![];
for oi in 1..inst.operands.len() {
let idx = match inst.operands[oi] {
Operand::IdRef(r) => r,
_ => continue,
};
let idx_ty = match value_result_type(ctx, idx) {
Some(t) => t,
None => continue,
};
let Some(signed) = int64_signedness(ctx, idx_ty) else {
continue; };
let _ = signed;
if let Some(v) = const_i64_value(ctx, idx) {
if u32::try_from(v).is_ok() {
let c32 = ctx.const_uint(v as u32);
inst.operands[oi] = Operand::IdRef(c32);
}
}
}
new_insts.extend(pre);
new_insts.push(inst);
}
ctx.module.functions[entry_idx].blocks[bi].instructions = new_insts;
}
}
pub(in crate::passes) fn normalize_int_arith_operand_widths(ctx: &mut Ctx) {
let mut int_width: HashMap<Word, u32> = HashMap::new();
for inst in ctx
.new_globals
.iter()
.chain(ctx.module.types_global_values.iter())
{
if inst.class.opcode == Op::TypeInt {
if let (Some(id), Some(Operand::LiteralBit32(w))) =
(inst.result_id, inst.operands.first())
{
int_width.insert(id, *w);
}
}
}
let mut id_ty: HashMap<Word, Word> = HashMap::new();
for inst in ctx
.new_globals
.iter()
.chain(ctx.module.types_global_values.iter())
{
if let (Some(r), Some(t)) = (inst.result_id, inst.result_type) {
id_ty.insert(r, t);
}
}
for f in &ctx.module.functions {
for p in &f.parameters {
if let (Some(r), Some(t)) = (p.result_id, p.result_type) {
id_ty.insert(r, t);
}
}
for b in &f.blocks {
for i in &b.instructions {
if let (Some(r), Some(t)) = (i.result_id, i.result_type) {
id_ty.insert(r, t);
}
}
}
}
let is_target = |op: Op| {
matches!(
op,
Op::IAdd
| Op::ISub
| Op::IMul
| Op::UDiv
| Op::SDiv
| Op::UMod
| Op::SRem
| Op::SMod
| Op::BitwiseAnd
| Op::BitwiseOr
| Op::BitwiseXor
| Op::SNegate
| Op::Not
)
};
for fi in 0..ctx.module.functions.len() {
for bi in 0..ctx.module.functions[fi].blocks.len() {
let insts = std::mem::take(&mut ctx.module.functions[fi].blocks[bi].instructions);
let mut new_insts: Vec<Instruction> = Vec::with_capacity(insts.len());
for mut inst in insts {
let coerce = is_target(inst.class.opcode)
.then_some(inst.result_type)
.flatten()
.and_then(|rt| int_width.get(&rt).copied().map(|w| (rt, w)));
if let Some((res_ty, res_w)) = coerce {
let mut truncated: HashMap<Word, Word> = HashMap::new();
for oi in 0..inst.operands.len() {
let Operand::IdRef(id) = inst.operands[oi] else {
continue;
};
let too_wide = id_ty
.get(&id)
.and_then(|t| int_width.get(t))
.is_some_and(|&ow| ow > res_w);
if !too_wide {
continue;
}
let narrow = if let Some(&existing) = truncated.get(&id) {
existing
} else {
let fresh = ctx.module.fresh_id();
new_insts.push(Instruction::new(
Op::UConvert,
Some(res_ty),
Some(fresh),
vec![Operand::IdRef(id)],
));
id_ty.insert(fresh, res_ty);
truncated.insert(id, fresh);
fresh
};
inst.operands[oi] = Operand::IdRef(narrow);
}
}
new_insts.push(inst);
}
ctx.module.functions[fi].blocks[bi].instructions = new_insts;
}
}
}
pub(in crate::passes) fn rewrite_scalar_pointer_arithmetic_access_chains(
ctx: &mut Ctx,
entry_idx: usize,
) {
let mut pointer_storage = HashMap::new();
for inst in ctx
.new_globals
.iter()
.chain(ctx.module.types_global_values.iter())
{
if inst.class.opcode == Op::TypePointer {
if let (Some(result), Some(Operand::StorageClass(storage))) =
(inst.result_id, inst.operands.first())
{
pointer_storage.insert(result, *storage);
}
}
}
let mut id_types = HashMap::new();
for inst in ctx
.new_globals
.iter()
.chain(ctx.module.types_global_values.iter())
{
if let (Some(result), Some(result_type)) = (inst.result_id, inst.result_type) {
id_types.insert(result, result_type);
}
}
for function in &ctx.module.functions {
for inst in &function.parameters {
if let (Some(result), Some(result_type)) = (inst.result_id, inst.result_type) {
id_types.insert(result, result_type);
}
}
for block in &function.blocks {
for inst in &block.instructions {
if let (Some(result), Some(result_type)) = (inst.result_id, inst.result_type) {
id_types.insert(result, result_type);
}
}
}
}
for block in &mut ctx.module.functions[entry_idx].blocks {
for inst in &mut block.instructions {
if inst.class.opcode != Op::InBoundsAccessChain {
continue;
}
let Some(result_type) = inst.result_type else {
continue;
};
if !pointer_storage
.get(&result_type)
.is_some_and(|storage| ptr_access_chain_allowed_storage(*storage))
{
continue;
}
let Some(Operand::IdRef(base)) = inst.operands.first() else {
continue;
};
if id_types.get(base) != Some(&result_type) {
continue;
}
*inst = Instruction::new(
Op::PtrAccessChain,
inst.result_type,
inst.result_id,
inst.operands.clone(),
);
}
}
}
pub(in crate::passes) fn decorate_ptr_access_chain_base_strides(ctx: &mut Ctx) {
let mut defs: HashMap<Word, Instruction> = HashMap::new();
for inst in ctx
.new_globals
.iter()
.chain(ctx.module.types_global_values.iter())
{
if let Some(result) = inst.result_id {
defs.insert(result, inst.clone());
}
}
let mut already: HashSet<Word> = HashSet::new();
for ann in &ctx.module.annotations {
if ann.class.opcode == Op::Decorate
&& ann.operands.get(1) == Some(&Operand::Decoration(Decoration::ArrayStride))
{
if let Some(Operand::IdRef(t)) = ann.operands.first() {
already.insert(*t);
}
}
}
let mut base_ptr_types: Vec<Word> = Vec::new();
for function in &ctx.module.functions {
for block in &function.blocks {
for inst in &block.instructions {
if inst.class.opcode != Op::PtrAccessChain {
continue;
}
if let Some(t) = inst.result_type {
if !base_ptr_types.contains(&t) {
base_ptr_types.push(t);
}
}
if let Some(Operand::IdRef(base)) = inst.operands.first() {
if let Some(t) = value_result_type(ctx, *base) {
if !base_ptr_types.contains(&t) {
base_ptr_types.push(t);
}
}
}
}
}
}
for ptr_ty in base_ptr_types {
if already.contains(&ptr_ty) {
continue;
}
let Some(def) = defs.get(&ptr_ty) else {
continue;
};
if def.class.opcode != Op::TypePointer {
continue;
}
let Some(Operand::StorageClass(storage)) = def.operands.first() else {
continue;
};
if matches!(storage, StorageClass::Workgroup) {
continue;
}
let Some(Operand::IdRef(pointee)) = def.operands.get(1) else {
continue;
};
let (size, align) = layout_ty_size_align(ctx, *pointee, &defs);
let stride = round_up(size, align).max(1);
ctx.module.annotations.push(Instruction::new(
Op::Decorate,
None,
None,
vec![
Operand::IdRef(ptr_ty),
Operand::Decoration(Decoration::ArrayStride),
Operand::LiteralBit32(stride),
],
));
already.insert(ptr_ty);
}
}
pub(in crate::passes) fn ptr_access_chain_allowed_storage(storage: StorageClass) -> bool {
matches!(
storage,
StorageClass::Workgroup | StorageClass::StorageBuffer | StorageClass::PhysicalStorageBuffer
)
}
pub(in crate::passes) fn walk_into_type(
ctx: &Ctx,
mut cur: Word,
indices: &[Operand],
) -> Option<Word> {
for op in indices {
let def = type_def_of(ctx, cur)?;
cur = match def.class.opcode {
Op::TypeStruct => {
let Operand::IdRef(idx_id) = op else {
return None;
};
let cdef = type_def_of(ctx, *idx_id)?;
if cdef.class.opcode != Op::Constant {
return None;
}
let member = match cdef.operands.first()? {
Operand::LiteralBit32(v) => *v as usize,
_ => return None,
};
match def.operands.get(member)? {
Operand::IdRef(m) => *m,
_ => return None,
}
}
Op::TypeArray | Op::TypeRuntimeArray | Op::TypeVector | Op::TypeMatrix => {
match def.operands.first()? {
Operand::IdRef(elem) => *elem,
_ => return None,
}
}
_ => return None,
};
}
Some(cur)
}
pub(in crate::passes) fn walk_into_type_partial(
ctx: &Ctx,
mut cur: Word,
indices: &[Operand],
) -> (Word, usize) {
for (n, op) in indices.iter().enumerate() {
let Some(def) = type_def_of(ctx, cur) else {
return (cur, n);
};
let next = match def.class.opcode {
Op::TypeStruct => {
let Operand::IdRef(idx_id) = op else {
return (cur, n);
};
let Some(cdef) = type_def_of(ctx, *idx_id) else {
return (cur, n);
};
if cdef.class.opcode != Op::Constant {
return (cur, n);
}
let member = match cdef.operands.first() {
Some(Operand::LiteralBit32(v)) => *v as usize,
_ => return (cur, n),
};
match def.operands.get(member) {
Some(Operand::IdRef(m)) => *m,
_ => return (cur, n),
}
}
Op::TypeArray | Op::TypeRuntimeArray | Op::TypeVector | Op::TypeMatrix => {
match def.operands.first() {
Some(Operand::IdRef(elem)) => *elem,
_ => return (cur, n),
}
}
_ => return (cur, n),
};
cur = next;
}
(cur, indices.len())
}
pub(in crate::passes) fn drop_overindexed_zero_tail(ctx: &mut Ctx, entry_idx: usize) {
let mut ptr_info: HashMap<Word, (StorageClass, Word)> = HashMap::new();
for inst in ctx
.new_globals
.iter()
.chain(ctx.module.types_global_values.iter())
{
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 edits: Vec<(usize, usize, usize)> = Vec::new();
for (bi, block) in ctx.module.functions[entry_idx].blocks.iter().enumerate() {
for (ii, inst) in block.instructions.iter().enumerate() {
if !matches!(inst.class.opcode, Op::InBoundsAccessChain | Op::AccessChain) {
continue;
}
let Some(result_type) = inst.result_type else {
continue;
};
let Some(&(_, result_pointee)) = ptr_info.get(&result_type) else {
continue;
};
let Some(Operand::IdRef(base)) = inst.operands.first() else {
continue;
};
let indices: Vec<Operand> = inst.operands[1..].to_vec();
if indices.is_empty() {
continue;
}
let Some(base_ptr_ty) = value_result_type(ctx, *base) else {
continue;
};
let Some(&(_, base_pointee)) = ptr_info.get(&base_ptr_ty) else {
continue;
};
let (reached, consumed) = walk_into_type_partial(ctx, base_pointee, &indices);
if consumed >= indices.len() || consumed == 0 {
continue;
}
let all_zero_tail = indices[consumed..].iter().all(|op| match op {
Operand::IdRef(id) => const_u32(ctx, *id) == Some(0),
_ => false,
});
if !all_zero_tail {
continue;
}
if reached != result_pointee || direct_scalar_width(ctx, reached).is_none() {
continue;
}
edits.push((bi, ii, consumed));
}
}
for (bi, ii, consumed) in edits {
ctx.module.functions[entry_idx].blocks[bi].instructions[ii]
.operands
.truncate(1 + consumed);
}
}
pub(in crate::passes) fn reroot_demoted_array_element_overindex(ctx: &mut Ctx, entry_idx: usize) {
let mut ptr_info: HashMap<Word, (StorageClass, Word)> = HashMap::new();
for inst in ctx
.new_globals
.iter()
.chain(ctx.module.types_global_values.iter())
{
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 func = ctx.module.functions[entry_idx].clone();
let mut edits: Vec<(usize, usize, Word)> = Vec::new();
for (bi, block) in func.blocks.iter().enumerate() {
for (ii, inst) in block.instructions.iter().enumerate() {
if !matches!(inst.class.opcode, Op::InBoundsAccessChain | Op::AccessChain) {
continue;
}
if inst.operands.len() != 2 {
continue;
}
let Some(result_type) = inst.result_type else {
continue;
};
let Some(&(_, result_pointee)) = ptr_info.get(&result_type) else {
continue;
};
let Some(Operand::IdRef(base)) = inst.operands.first() else {
continue;
};
let Some(base_ptr_ty) = value_result_type(ctx, *base) else {
continue;
};
let Some(&(base_sc, base_pointee)) = ptr_info.get(&base_ptr_ty) else {
continue;
};
if base_pointee != result_pointee || direct_scalar_width(ctx, base_pointee).is_none() {
continue;
}
let Some(array_id) =
trace_to_array_element_zero(ctx, &func, *base, base_pointee, base_sc, &ptr_info)
else {
continue;
};
edits.push((bi, ii, array_id));
}
}
for (bi, ii, array_id) in edits {
ctx.module.functions[entry_idx].blocks[bi].instructions[ii].operands[0] =
Operand::IdRef(array_id);
}
}