use super::*;
pub(in crate::passes) fn rewrite_dynamic_struct_index_reinterpret(
ctx: &mut Ctx,
entry_idx: usize,
) -> Result<(), String> {
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 struct_single_array_elem = |ctx: &Ctx, struct_ty: Word| -> Option<Word> {
let def = type_def_of(ctx, struct_ty)?;
if def.class.opcode != Op::TypeStruct || def.operands.len() != 1 {
return None;
}
let Operand::IdRef(member0) = def.operands.first()? else {
return None;
};
let mdef = type_def_of(ctx, *member0)?;
if !matches!(mdef.class.opcode, Op::TypeArray | Op::TypeRuntimeArray) {
return None;
}
let Operand::IdRef(elem) = mdef.operands.first()? else {
return None;
};
direct_scalar_width(ctx, *elem).map(|_| *elem)
};
struct Plan {
bi: usize,
ii: usize,
ac_id: Word,
base: Word,
dyn_idx: Word,
elem_ty: Word,
result_pointee: Word,
sc: StorageClass,
reinterpret: bool,
}
let mut plans: Vec<Plan> = Vec::new();
let blocks = &ctx.module.functions[entry_idx].blocks;
for (bi, block) in 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(ac_id), Some(result_type)) = (inst.result_id, inst.result_type) else {
continue;
};
let Some(&(sc, result_pointee)) = ptr_info.get(&result_type) else {
continue;
};
let Some(Operand::IdRef(base)) = inst.operands.first() else {
continue;
};
let Some(Operand::IdRef(dyn_idx)) = inst.operands.get(1) else {
continue;
};
if const_u32(ctx, *dyn_idx).is_some() {
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 Some(elem_ty) = struct_single_array_elem(ctx, base_pointee) else {
continue;
};
let (Some(ew), Some(pw)) = (
direct_scalar_width(ctx, elem_ty),
direct_scalar_width(ctx, result_pointee),
) else {
continue;
};
if ew != pw {
continue;
}
plans.push(Plan {
bi,
ii,
ac_id,
base: *base,
dyn_idx: *dyn_idx,
elem_ty,
result_pointee,
sc,
reinterpret: result_pointee != elem_ty,
});
}
}
if plans.is_empty() {
return Ok(());
}
let mut use_sites: HashMap<Word, Vec<(usize, usize, bool)>> = HashMap::new();
let mut disqualified: HashSet<Word> = HashSet::new();
let plan_ids: HashSet<Word> = plans.iter().map(|p| p.ac_id).collect();
let result_pointee_of: HashMap<Word, Word> =
plans.iter().map(|p| (p.ac_id, p.result_pointee)).collect();
for (bi, block) in ctx.module.functions[entry_idx].blocks.iter().enumerate() {
for (ii, inst) in block.instructions.iter().enumerate() {
if inst
.result_id
.map(|r| plan_ids.contains(&r))
.unwrap_or(false)
&& matches!(inst.class.opcode, Op::InBoundsAccessChain | Op::AccessChain)
{
continue;
}
match inst.class.opcode {
Op::Load => {
if let Some(Operand::IdRef(ptr)) = inst.operands.first() {
if plan_ids.contains(ptr) {
if inst.result_type == result_pointee_of.get(ptr).copied() {
use_sites.entry(*ptr).or_default().push((bi, ii, true));
} else {
disqualified.insert(*ptr);
}
}
}
}
Op::Store => {
if let Some(Operand::IdRef(ptr)) = inst.operands.first() {
if plan_ids.contains(ptr) {
let obj = match inst.operands.get(1) {
Some(Operand::IdRef(o)) => value_result_type(ctx, *o),
_ => None,
};
if obj == result_pointee_of.get(ptr).copied() {
use_sites.entry(*ptr).or_default().push((bi, ii, false));
} else {
disqualified.insert(*ptr);
}
}
}
}
_ => {
for op in &inst.operands {
if let Operand::IdRef(id) = op {
if plan_ids.contains(id) {
disqualified.insert(*id);
}
}
}
}
}
}
}
plans.retain(|p| !disqualified.contains(&p.ac_id) && use_sites.contains_key(&p.ac_id));
if plans.is_empty() {
return Ok(());
}
let member0 = ctx.const_uint(0);
let mut new_ptr_ty: HashMap<(StorageClass, Word), Word> = HashMap::new();
for p in &plans {
new_ptr_ty
.entry((p.sc, p.elem_ty))
.or_insert_with(|| ctx.ty_ptr(p.sc, p.elem_ty));
}
let load_split_id: HashMap<(usize, usize), Word> = plans
.iter()
.flat_map(|p| use_sites.get(&p.ac_id).into_iter().flatten())
.filter(|(_, _, is_load)| *is_load)
.map(|&(bi, ii, _)| ((bi, ii), 0))
.collect::<HashMap<_, _>>()
.into_keys()
.map(|k| (k, ctx.module.fresh_id()))
.collect();
let store_cast_id: HashMap<(usize, usize), Word> = plans
.iter()
.flat_map(|p| use_sites.get(&p.ac_id).into_iter().flatten())
.filter(|(_, _, is_load)| !*is_load)
.map(|&(bi, ii, _)| ((bi, ii), 0))
.collect::<HashMap<_, _>>()
.into_keys()
.map(|k| (k, ctx.module.fresh_id()))
.collect();
let ac_at: HashMap<(usize, usize), &Plan> = plans.iter().map(|p| ((p.bi, p.ii), p)).collect();
let elem_ty_of_load: HashMap<(usize, usize), (Word, Word)> = plans
.iter()
.flat_map(|p| {
use_sites
.get(&p.ac_id)
.into_iter()
.flatten()
.map(move |&(bi, ii, is_load)| ((bi, ii), (p.elem_ty, p.result_pointee, is_load)))
})
.map(|((bi, ii), (e, rp, _))| ((bi, ii), (e, rp)))
.collect();
let n_blocks = ctx.module.functions[entry_idx].blocks.len();
for bi in 0..n_blocks {
let old = std::mem::take(&mut ctx.module.functions[entry_idx].blocks[bi].instructions);
let mut newv: Vec<Instruction> = Vec::with_capacity(old.len() + 4);
for (ii, inst) in old.into_iter().enumerate() {
if let Some(p) = ac_at.get(&(bi, ii)) {
let new_ty = new_ptr_ty[&(p.sc, p.elem_ty)];
newv.push(Instruction::new(
inst.class.opcode,
Some(new_ty),
Some(p.ac_id),
vec![
Operand::IdRef(p.base),
Operand::IdRef(member0),
Operand::IdRef(p.dyn_idx),
],
));
let _ = p.reinterpret;
continue;
}
if let Some(&load_id) = load_split_id.get(&(bi, ii)) {
let (elem_ty, result_pointee) = elem_ty_of_load[&(bi, ii)];
let ptr = match inst.operands.first() {
Some(Operand::IdRef(p)) => *p,
_ => return Err("load split site lost its pointer operand".to_string()),
};
let res = inst.result_id.ok_or("load has a result id")?;
let mut load_ops = vec![Operand::IdRef(ptr)];
load_ops.extend(inst.operands.iter().skip(1).cloned());
newv.push(Instruction::new(
Op::Load,
Some(elem_ty),
Some(load_id),
load_ops,
));
if elem_ty == result_pointee {
newv.push(Instruction::new(
Op::CopyObject,
Some(result_pointee),
Some(res),
vec![Operand::IdRef(load_id)],
));
} else {
newv.push(Instruction::new(
Op::Bitcast,
Some(result_pointee),
Some(res),
vec![Operand::IdRef(load_id)],
));
}
continue;
}
if let Some(&cast_id) = store_cast_id.get(&(bi, ii)) {
let (elem_ty, _result_pointee) = elem_ty_of_load[&(bi, ii)];
let ptr = match inst.operands.first() {
Some(Operand::IdRef(p)) => *p,
_ => return Err("store cast site lost its pointer operand".to_string()),
};
let obj = match inst.operands.get(1) {
Some(Operand::IdRef(o)) => *o,
_ => return Err("store cast site lost its object operand".to_string()),
};
newv.push(Instruction::new(
Op::Bitcast,
Some(elem_ty),
Some(cast_id),
vec![Operand::IdRef(obj)],
));
let mut store_ops = vec![Operand::IdRef(ptr), Operand::IdRef(cast_id)];
store_ops.extend(inst.operands.iter().skip(2).cloned());
newv.push(Instruction::new(Op::Store, None, None, store_ops));
continue;
}
newv.push(inst);
}
ctx.module.functions[entry_idx].blocks[bi].instructions = newv;
}
Ok(())
}
pub(in crate::passes) fn rewrite_dynamic_struct_index_vector_reinterpret(
ctx: &mut Ctx,
entry_idx: usize,
) -> Result<(), String> {
#[derive(Clone, Copy)]
struct Plan {
bi: usize,
ii: usize,
opcode: Op,
ac_id: Word,
base: Word,
dyn_idx: Word,
index_ty: Word,
elem_ty: Word,
component_ty: Word,
result_pointee: Word,
sc: StorageClass,
lanes: u32,
}
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(sc)), Some(Operand::IdRef(pointee))) =
(inst.result_id, inst.operands.first(), inst.operands.get(1))
{
ptr_info.insert(id, (*sc, *pointee));
}
}
}
let vector_shape = |ctx: &Ctx, ty: Word| -> Option<(Word, u32)> {
let def = type_def_of(ctx, ty)?;
if def.class.opcode != Op::TypeVector {
return None;
}
let (Operand::IdRef(component), Operand::LiteralBit32(lanes)) =
(def.operands.first()?, def.operands.get(1)?)
else {
return None;
};
(*lanes >= 2 && direct_scalar_width(ctx, *component).is_some())
.then_some((*component, *lanes))
};
let integer_index = |ctx: &Ctx, ty: Word| {
type_def_of(ctx, ty).is_some_and(|def| def.class.opcode == Op::TypeInt)
};
let mut plans = 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)
|| inst.operands.len() != 2
{
continue;
}
let (Some(ac_id), Some(result_ptr_ty)) = (inst.result_id, inst.result_type) else {
continue;
};
let Some(&(sc, result_pointee)) = ptr_info.get(&result_ptr_ty) else {
continue;
};
let Some((component_ty, lanes)) = vector_shape(ctx, result_pointee) else {
continue;
};
let (Some(Operand::IdRef(base)), Some(Operand::IdRef(dyn_idx))) =
(inst.operands.first(), inst.operands.get(1))
else {
continue;
};
if const_u32(ctx, *dyn_idx).is_some() {
continue;
}
let Some(index_ty) = value_result_type(ctx, *dyn_idx) else {
continue;
};
if !integer_index(ctx, index_ty) {
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 Some(elem_ty) = single_member_array_scalar_elem(ctx, base_pointee) else {
continue;
};
if direct_scalar_width(ctx, elem_ty) != direct_scalar_width(ctx, component_ty) {
continue;
}
if walk_into_type(ctx, base_pointee, &inst.operands[1..]).is_some() {
continue;
}
plans.push(Plan {
bi,
ii,
opcode: inst.class.opcode,
ac_id,
base: *base,
dyn_idx: *dyn_idx,
index_ty,
elem_ty,
component_ty,
result_pointee,
sc,
lanes,
});
}
}
if plans.is_empty() {
return Ok(());
}
let plan_ids: HashSet<Word> = plans.iter().map(|p| p.ac_id).collect();
let result_pointee_of: HashMap<Word, Word> =
plans.iter().map(|p| (p.ac_id, p.result_pointee)).collect();
let mut use_sites: HashMap<Word, Vec<(usize, usize, bool)>> = HashMap::new();
let mut disqualified = HashSet::new();
for (bi, block) in ctx.module.functions[entry_idx].blocks.iter().enumerate() {
for (ii, inst) in block.instructions.iter().enumerate() {
if inst.result_id.is_some_and(|id| plan_ids.contains(&id))
&& matches!(inst.class.opcode, Op::InBoundsAccessChain | Op::AccessChain)
{
continue;
}
for chain_id in inst.operands.iter().filter_map(|operand| match operand {
Operand::IdRef(id) if plan_ids.contains(id) => Some(*id),
_ => None,
}) {
let exact_load = inst.class.opcode == Op::Load
&& inst.operands.len() == 1
&& inst.result_type == result_pointee_of.get(&chain_id).copied();
let exact_store = inst.class.opcode == Op::Store
&& inst.operands.len() == 2
&& matches!(inst.operands.first(), Some(Operand::IdRef(id)) if *id == chain_id)
&& inst.operands.get(1).and_then(|operand| match operand {
Operand::IdRef(value) => value_result_type(ctx, *value),
_ => None,
}) == result_pointee_of.get(&chain_id).copied();
if exact_load {
use_sites.entry(chain_id).or_default().push((bi, ii, true));
} else if exact_store {
use_sites.entry(chain_id).or_default().push((bi, ii, false));
} else {
disqualified.insert(chain_id);
}
}
}
}
plans.retain(|plan| !disqualified.contains(&plan.ac_id) && use_sites.contains_key(&plan.ac_id));
if plans.is_empty() {
return Ok(());
}
let member0 = ctx.const_uint(0);
let mut elem_ptr_ty = HashMap::new();
for plan in &plans {
elem_ptr_ty
.entry((plan.sc, plan.elem_ty))
.or_insert_with(|| ctx.ty_ptr(plan.sc, plan.elem_ty));
}
let plan_by_id: HashMap<Word, Plan> = plans.iter().map(|plan| (plan.ac_id, *plan)).collect();
let chain_at: HashMap<(usize, usize), Word> = plans
.iter()
.map(|plan| ((plan.bi, plan.ii), plan.ac_id))
.collect();
let use_at: HashMap<(usize, usize), Word> = plans
.iter()
.flat_map(|plan| {
use_sites
.get(&plan.ac_id)
.into_iter()
.flatten()
.map(move |&(bi, ii, _)| ((bi, ii), plan.ac_id))
})
.collect();
let n_blocks = ctx.module.functions[entry_idx].blocks.len();
for bi in 0..n_blocks {
let old = std::mem::take(&mut ctx.module.functions[entry_idx].blocks[bi].instructions);
let mut rewritten = Vec::with_capacity(old.len() + 8);
for (ii, inst) in old.into_iter().enumerate() {
if chain_at.contains_key(&(bi, ii)) {
continue;
}
let Some(chain_id) = use_at.get(&(bi, ii)).copied() else {
rewritten.push(inst);
continue;
};
let plan = plan_by_id[&chain_id];
let lane_base = ctx.module.fresh_id();
let lane_factor = ctx.const_int_of(plan.index_ty, i64::from(plan.lanes));
rewritten.push(Instruction::new(
Op::IMul,
Some(plan.index_ty),
Some(lane_base),
vec![Operand::IdRef(plan.dyn_idx), Operand::IdRef(lane_factor)],
));
let mut lane_ptrs = Vec::with_capacity(plan.lanes as usize);
for lane in 0..plan.lanes {
let lane_index = if lane == 0 {
lane_base
} else {
let lane_offset = ctx.const_int_of(plan.index_ty, i64::from(lane));
let lane_index = ctx.module.fresh_id();
rewritten.push(Instruction::new(
Op::IAdd,
Some(plan.index_ty),
Some(lane_index),
vec![Operand::IdRef(lane_base), Operand::IdRef(lane_offset)],
));
lane_index
};
let ptr = ctx.module.fresh_id();
rewritten.push(Instruction::new(
plan.opcode,
Some(elem_ptr_ty[&(plan.sc, plan.elem_ty)]),
Some(ptr),
vec![
Operand::IdRef(plan.base),
Operand::IdRef(member0),
Operand::IdRef(lane_index),
],
));
lane_ptrs.push(ptr);
}
match inst.class.opcode {
Op::Load => {
let result = inst.result_id.ok_or("vector load has a result id")?;
let mut components = Vec::with_capacity(plan.lanes as usize);
for ptr in lane_ptrs {
let loaded = ctx.module.fresh_id();
rewritten.push(Instruction::new(
Op::Load,
Some(plan.elem_ty),
Some(loaded),
vec![Operand::IdRef(ptr)],
));
let component = if plan.elem_ty == plan.component_ty {
loaded
} else {
let cast = ctx.module.fresh_id();
rewritten.push(Instruction::new(
Op::Bitcast,
Some(plan.component_ty),
Some(cast),
vec![Operand::IdRef(loaded)],
));
cast
};
components.push(Operand::IdRef(component));
}
rewritten.push(Instruction::new(
Op::CompositeConstruct,
Some(plan.result_pointee),
Some(result),
components,
));
}
Op::Store => {
let object = match inst.operands.get(1) {
Some(Operand::IdRef(value)) => *value,
_ => return Err("vector store lost its object operand".to_string()),
};
for (lane, ptr) in lane_ptrs.into_iter().enumerate() {
let component = ctx.module.fresh_id();
rewritten.push(Instruction::new(
Op::CompositeExtract,
Some(plan.component_ty),
Some(component),
vec![Operand::IdRef(object), Operand::LiteralBit32(lane as u32)],
));
let element = if plan.elem_ty == plan.component_ty {
component
} else {
let cast = ctx.module.fresh_id();
rewritten.push(Instruction::new(
Op::Bitcast,
Some(plan.elem_ty),
Some(cast),
vec![Operand::IdRef(component)],
));
cast
};
rewritten.push(Instruction::new(
Op::Store,
None,
None,
vec![Operand::IdRef(ptr), Operand::IdRef(element)],
));
}
}
_ => return Err("vector dynamic-chain use was not load/store".to_string()),
}
}
ctx.module.functions[entry_idx].blocks[bi].instructions = rewritten;
}
Ok(())
}
pub(in crate::passes) fn single_member_array_scalar_elem(
ctx: &Ctx,
struct_ty: Word,
) -> Option<Word> {
let def = type_def_of(ctx, struct_ty)?;
if def.class.opcode != Op::TypeStruct || def.operands.len() != 1 {
return None;
}
let Operand::IdRef(member0) = def.operands.first()? else {
return None;
};
let mdef = type_def_of(ctx, *member0)?;
if !matches!(mdef.class.opcode, Op::TypeArray | Op::TypeRuntimeArray) {
return None;
}
let Operand::IdRef(elem) = mdef.operands.first()? else {
return None;
};
direct_scalar_width(ctx, *elem).map(|_| *elem)
}
pub(in crate::passes) fn rewrite_chained_element_reinterpret(
ctx: &mut Ctx,
entry_idx: usize,
) -> Result<(), String> {
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 chain_def: HashMap<Word, Instruction> = HashMap::new();
for block in &ctx.module.functions[entry_idx].blocks {
for inst in &block.instructions {
if matches!(inst.class.opcode, Op::InBoundsAccessChain | Op::AccessChain) {
if let Some(rid) = inst.result_id {
chain_def.insert(rid, inst.clone());
}
}
}
}
let two_index_zero_lead = |ctx: &Ctx, inst: &Instruction| -> Option<(Word, Word)> {
if inst.operands.len() != 3 {
return None;
}
let (Operand::IdRef(base), Operand::IdRef(i0), Operand::IdRef(i1)) =
(&inst.operands[0], &inst.operands[1], &inst.operands[2])
else {
return None;
};
if const_u32(ctx, *i0) != Some(0) {
return None;
}
Some((*base, *i1))
};
struct Plan {
bi: usize,
ii: usize,
ac_id: Word,
buf: Word,
inner_idx: Word,
out_idx: Word,
elem_ty: Word,
result_pointee: Word,
sc: StorageClass,
}
let mut plans: Vec<Plan> = 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(ac_id), Some(result_type)) = (inst.result_id, inst.result_type) else {
continue;
};
let Some((inner, out_idx)) = two_index_zero_lead(ctx, inst) else {
continue;
};
let Some(&(sc, result_pointee)) = ptr_info.get(&result_type) else {
continue;
};
if !ptr_access_chain_allowed_storage(sc) {
continue;
}
let Some(pw) = direct_scalar_width(ctx, result_pointee) else {
continue;
};
let Some(inner_inst) = chain_def.get(&inner) else {
continue;
};
let Some((buf, inner_idx)) = two_index_zero_lead(ctx, inner_inst) else {
continue;
};
let Some(inner_result_type) = inner_inst.result_type else {
continue;
};
let Some(&(_, inner_pointee)) = ptr_info.get(&inner_result_type) else {
continue;
};
let Some(buf_ptr_ty) = value_result_type(ctx, buf) else {
continue;
};
let Some(&(_, buf_pointee)) = ptr_info.get(&buf_ptr_ty) else {
continue;
};
let Some(elem_ty) = single_member_array_scalar_elem(ctx, buf_pointee) else {
continue;
};
if inner_pointee != elem_ty {
continue;
}
if direct_scalar_width(ctx, elem_ty) != Some(pw) {
continue;
}
if walk_into_type(ctx, inner_pointee, &inst.operands[1..]).is_some() {
continue;
}
plans.push(Plan {
bi,
ii,
ac_id,
buf,
inner_idx,
out_idx,
elem_ty,
result_pointee,
sc,
});
}
}
if plans.is_empty() {
return Ok(());
}
let plan_ids: HashSet<Word> = plans.iter().map(|p| p.ac_id).collect();
let result_pointee_of: HashMap<Word, Word> =
plans.iter().map(|p| (p.ac_id, p.result_pointee)).collect();
let mut use_sites: HashMap<Word, Vec<(usize, usize, bool)>> = HashMap::new();
let mut disqualified: HashSet<Word> = HashSet::new();
for (bi, block) in ctx.module.functions[entry_idx].blocks.iter().enumerate() {
for (ii, inst) in block.instructions.iter().enumerate() {
if inst
.result_id
.map(|r| plan_ids.contains(&r))
.unwrap_or(false)
&& matches!(inst.class.opcode, Op::InBoundsAccessChain | Op::AccessChain)
{
continue;
}
match inst.class.opcode {
Op::Load => {
if let Some(Operand::IdRef(ptr)) = inst.operands.first() {
if plan_ids.contains(ptr) {
if inst.result_type == result_pointee_of.get(ptr).copied() {
use_sites.entry(*ptr).or_default().push((bi, ii, true));
} else {
disqualified.insert(*ptr);
}
}
}
}
Op::Store => {
if let Some(Operand::IdRef(ptr)) = inst.operands.first() {
if plan_ids.contains(ptr) {
let obj = match inst.operands.get(1) {
Some(Operand::IdRef(o)) => value_result_type(ctx, *o),
_ => None,
};
if obj == result_pointee_of.get(ptr).copied() {
use_sites.entry(*ptr).or_default().push((bi, ii, false));
} else {
disqualified.insert(*ptr);
}
}
}
}
_ => {
for op in &inst.operands {
if let Operand::IdRef(id) = op {
if plan_ids.contains(id) {
disqualified.insert(*id);
}
}
}
}
}
}
}
plans.retain(|p| !disqualified.contains(&p.ac_id) && use_sites.contains_key(&p.ac_id));
if plans.is_empty() {
return Ok(());
}
let member0 = ctx.const_uint(0);
let uint_ty = ctx.ty_uint();
let mut new_ptr_ty: HashMap<(StorageClass, Word), Word> = HashMap::new();
let mut sum_id: HashMap<Word, Word> = HashMap::new();
for p in &plans {
new_ptr_ty
.entry((p.sc, p.elem_ty))
.or_insert_with(|| ctx.ty_ptr(p.sc, p.elem_ty));
sum_id.insert(p.ac_id, ctx.module.fresh_id());
}
let load_split_id: HashMap<(usize, usize), Word> = plans
.iter()
.flat_map(|p| use_sites.get(&p.ac_id).into_iter().flatten())
.filter(|(_, _, is_load)| *is_load)
.map(|&(bi, ii, _)| ((bi, ii), 0))
.collect::<HashMap<_, _>>()
.into_keys()
.map(|k| (k, ctx.module.fresh_id()))
.collect();
let store_cast_id: HashMap<(usize, usize), Word> = plans
.iter()
.flat_map(|p| use_sites.get(&p.ac_id).into_iter().flatten())
.filter(|(_, _, is_load)| !*is_load)
.map(|&(bi, ii, _)| ((bi, ii), 0))
.collect::<HashMap<_, _>>()
.into_keys()
.map(|k| (k, ctx.module.fresh_id()))
.collect();
let ac_at: HashMap<(usize, usize), &Plan> = plans.iter().map(|p| ((p.bi, p.ii), p)).collect();
let elem_ty_of_use: HashMap<(usize, usize), (Word, Word)> = plans
.iter()
.flat_map(|p| {
use_sites
.get(&p.ac_id)
.into_iter()
.flatten()
.map(move |&(bi, ii, _)| ((bi, ii), (p.elem_ty, p.result_pointee)))
})
.collect();
let n_blocks = ctx.module.functions[entry_idx].blocks.len();
for bi in 0..n_blocks {
let old = std::mem::take(&mut ctx.module.functions[entry_idx].blocks[bi].instructions);
let mut newv: Vec<Instruction> = Vec::with_capacity(old.len() + 4);
for (ii, inst) in old.into_iter().enumerate() {
if let Some(p) = ac_at.get(&(bi, ii)) {
let sum = sum_id[&p.ac_id];
newv.push(Instruction::new(
Op::IAdd,
Some(uint_ty),
Some(sum),
vec![Operand::IdRef(p.inner_idx), Operand::IdRef(p.out_idx)],
));
let new_ty = new_ptr_ty[&(p.sc, p.elem_ty)];
newv.push(Instruction::new(
inst.class.opcode,
Some(new_ty),
Some(p.ac_id),
vec![
Operand::IdRef(p.buf),
Operand::IdRef(member0),
Operand::IdRef(sum),
],
));
continue;
}
if let Some(&load_id) = load_split_id.get(&(bi, ii)) {
let (elem_ty, result_pointee) = elem_ty_of_use[&(bi, ii)];
let ptr = match inst.operands.first() {
Some(Operand::IdRef(p)) => *p,
_ => return Err("load split site lost its pointer operand".to_string()),
};
let res = inst.result_id.ok_or("load has a result id")?;
let mut load_ops = vec![Operand::IdRef(ptr)];
load_ops.extend(inst.operands.iter().skip(1).cloned());
newv.push(Instruction::new(
Op::Load,
Some(elem_ty),
Some(load_id),
load_ops,
));
newv.push(Instruction::new(
Op::Bitcast,
Some(result_pointee),
Some(res),
vec![Operand::IdRef(load_id)],
));
continue;
}
if let Some(&cast_id) = store_cast_id.get(&(bi, ii)) {
let (elem_ty, _result_pointee) = elem_ty_of_use[&(bi, ii)];
let ptr = match inst.operands.first() {
Some(Operand::IdRef(p)) => *p,
_ => return Err("store cast site lost its pointer operand".to_string()),
};
let obj = match inst.operands.get(1) {
Some(Operand::IdRef(o)) => *o,
_ => return Err("store cast site lost its object operand".to_string()),
};
newv.push(Instruction::new(
Op::Bitcast,
Some(elem_ty),
Some(cast_id),
vec![Operand::IdRef(obj)],
));
let mut store_ops = vec![Operand::IdRef(ptr), Operand::IdRef(cast_id)];
store_ops.extend(inst.operands.iter().skip(2).cloned());
newv.push(Instruction::new(Op::Store, None, None, store_ops));
continue;
}
newv.push(inst);
}
ctx.module.functions[entry_idx].blocks[bi].instructions = newv;
}
Ok(())
}
pub(in crate::passes) fn rewrite_byte_buffer_chained_reinterpret(
ctx: &mut Ctx,
entry_idx: usize,
) -> Result<(), String> {
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 is_int = |ctx: &Ctx, ty: Word| -> bool {
type_def_of(ctx, ty)
.map(|d| d.class.opcode == Op::TypeInt)
.unwrap_or(false)
};
let mut chain_def: HashMap<Word, Instruction> = HashMap::new();
for block in &ctx.module.functions[entry_idx].blocks {
for inst in &block.instructions {
if matches!(inst.class.opcode, Op::InBoundsAccessChain | Op::AccessChain) {
if let Some(rid) = inst.result_id {
chain_def.insert(rid, inst.clone());
}
}
}
}
struct Plan {
bi: usize,
ii: usize,
ac_id: Word,
buf: Word,
inner_idx: Word,
elem_ty: Word,
result_pointee: Word,
ratio: u32,
slot_w: u32,
}
let mut plans: Vec<Plan> = 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(ac_id), Some(result_type)) = (inst.result_id, inst.result_type) else {
continue;
};
if inst.operands.len() != 2 {
continue;
}
let (Operand::IdRef(inner), Operand::IdRef(_out_idx)) =
(&inst.operands[0], &inst.operands[1])
else {
continue;
};
let Some(&(sc, result_pointee)) = ptr_info.get(&result_type) else {
continue;
};
if sc != StorageClass::StorageBuffer {
continue;
}
let Some(w) = direct_scalar_width(ctx, result_pointee) else {
continue;
};
if !is_int(ctx, result_pointee) {
continue;
}
let Some(inner_inst) = chain_def.get(inner) else {
continue;
};
if inner_inst.operands.len() != 3 {
continue;
}
let (Operand::IdRef(buf), Operand::IdRef(i0), Operand::IdRef(inner_idx)) = (
&inner_inst.operands[0],
&inner_inst.operands[1],
&inner_inst.operands[2],
) else {
continue;
};
if const_u32(ctx, *i0) != Some(0) {
continue;
}
let Some(inner_result_type) = inner_inst.result_type else {
continue;
};
let Some(&(_, inner_pointee)) = ptr_info.get(&inner_result_type) else {
continue;
};
let Some(buf_ptr_ty) = value_result_type(ctx, *buf) else {
continue;
};
let Some(&(_, buf_pointee)) = ptr_info.get(&buf_ptr_ty) else {
continue;
};
let Some(elem_ty) = single_member_array_scalar_elem(ctx, buf_pointee) else {
continue;
};
if inner_pointee != elem_ty || !is_int(ctx, elem_ty) {
continue;
}
let Some(v) = direct_scalar_width(ctx, elem_ty) else {
continue;
};
if v == 0 || w % v != 0 {
continue;
}
let ratio = w / v;
if ratio < 2 {
continue;
}
if walk_into_type(ctx, inner_pointee, &inst.operands[1..]).is_some() {
continue;
}
plans.push(Plan {
bi,
ii,
ac_id,
buf: *buf,
inner_idx: *inner_idx,
elem_ty,
result_pointee,
ratio,
slot_w: v,
});
}
}
if plans.is_empty() {
return Ok(());
}
let plan_ids: HashSet<Word> = plans.iter().map(|p| p.ac_id).collect();
let result_pointee_of: HashMap<Word, Word> =
plans.iter().map(|p| (p.ac_id, p.result_pointee)).collect();
let mut use_sites: HashMap<Word, Vec<(usize, usize, bool)>> = HashMap::new();
let mut disqualified: HashSet<Word> = HashSet::new();
for (bi, block) in ctx.module.functions[entry_idx].blocks.iter().enumerate() {
for (ii, inst) in block.instructions.iter().enumerate() {
if inst
.result_id
.map(|r| plan_ids.contains(&r))
.unwrap_or(false)
&& matches!(inst.class.opcode, Op::InBoundsAccessChain | Op::AccessChain)
{
continue;
}
match inst.class.opcode {
Op::Load => {
if let Some(Operand::IdRef(ptr)) = inst.operands.first() {
if plan_ids.contains(ptr) {
if inst.result_type == result_pointee_of.get(ptr).copied() {
use_sites.entry(*ptr).or_default().push((bi, ii, true));
} else {
disqualified.insert(*ptr);
}
}
}
}
Op::Store => {
if let Some(Operand::IdRef(ptr)) = inst.operands.first() {
if plan_ids.contains(ptr) {
let obj = match inst.operands.get(1) {
Some(Operand::IdRef(o)) => value_result_type(ctx, *o),
_ => None,
};
if obj == result_pointee_of.get(ptr).copied() {
use_sites.entry(*ptr).or_default().push((bi, ii, false));
} else {
disqualified.insert(*ptr);
}
}
}
}
_ => {
for op in &inst.operands {
if let Operand::IdRef(id) = op {
if plan_ids.contains(id) {
disqualified.insert(*id);
}
}
}
}
}
}
}
plans.retain(|p| !disqualified.contains(&p.ac_id) && use_sites.contains_key(&p.ac_id));
if plans.is_empty() {
return Ok(());
}
let member0 = ctx.const_uint(0);
let uint_ty = ctx.ty_uint();
let mut elem_ptr_ty: HashMap<Word, Word> = HashMap::new();
let mut sum_id: HashMap<Word, Word> = HashMap::new();
let mut ratio_const: HashMap<u32, Word> = HashMap::new();
for p in &plans {
elem_ptr_ty
.entry(p.elem_ty)
.or_insert_with(|| ctx.ty_ptr(StorageClass::StorageBuffer, p.elem_ty));
sum_id.insert(p.ac_id, ctx.module.fresh_id());
ratio_const
.entry(p.ratio)
.or_insert_with(|| ctx.const_uint(p.ratio));
}
let ac_at: HashMap<(usize, usize), Word> =
plans.iter().map(|p| ((p.bi, p.ii), p.ac_id)).collect();
let plan_by_id: HashMap<Word, (Word, Word, Word, Word, u32, u32)> = plans
.iter()
.map(|p| {
(
p.ac_id,
(
p.buf,
p.elem_ty,
p.result_pointee,
p.inner_idx,
p.ratio,
p.slot_w,
),
)
})
.collect();
let use_at: HashMap<(usize, usize), Word> = plans
.iter()
.flat_map(|p| {
use_sites
.get(&p.ac_id)
.into_iter()
.flatten()
.map(move |&(bi, ii, _)| ((bi, ii), p.ac_id))
})
.collect();
let n_blocks = ctx.module.functions[entry_idx].blocks.len();
for bi in 0..n_blocks {
let old = std::mem::take(&mut ctx.module.functions[entry_idx].blocks[bi].instructions);
let mut newv: Vec<Instruction> = Vec::with_capacity(old.len() + 8);
for (ii, inst) in old.into_iter().enumerate() {
if let Some(&ac_id) = ac_at.get(&(bi, ii)) {
let (_buf, _e, _p, inner_idx, ratio, _v) = plan_by_id[&ac_id];
let sum = sum_id[&ac_id];
let out_idx = match inst.operands.get(1) {
Some(Operand::IdRef(o)) => *o,
_ => {
return Err(
"byte-buffer reinterpret chain lost its index operand".to_string()
)
}
};
let mul = ctx.module.fresh_id();
newv.push(Instruction::new(
Op::IMul,
Some(uint_ty),
Some(mul),
vec![Operand::IdRef(out_idx), Operand::IdRef(ratio_const[&ratio])],
));
newv.push(Instruction::new(
Op::IAdd,
Some(uint_ty),
Some(sum),
vec![Operand::IdRef(inner_idx), Operand::IdRef(mul)],
));
continue;
}
if let Some(&ac_id) = use_at.get(&(bi, ii)) {
let (buf, elem_ty, result_pointee, _ii, ratio, slot_w) = plan_by_id[&ac_id];
let sum = sum_id[&ac_id];
let eptr = elem_ptr_ty[&elem_ty];
let mut slot_ptr = Vec::with_capacity(ratio as usize);
for j in 0..ratio {
let idx = if j == 0 {
sum
} else {
let off = ctx.const_uint(j);
let id = ctx.module.fresh_id();
newv.push(Instruction::new(
Op::IAdd,
Some(uint_ty),
Some(id),
vec![Operand::IdRef(sum), Operand::IdRef(off)],
));
id
};
let pid = ctx.module.fresh_id();
newv.push(Instruction::new(
Op::InBoundsAccessChain,
Some(eptr),
Some(pid),
vec![
Operand::IdRef(buf),
Operand::IdRef(member0),
Operand::IdRef(idx),
],
));
slot_ptr.push(pid);
}
if inst.class.opcode == Op::Load {
let res = inst.result_id.ok_or("load has a result id")?;
let mut acc: Option<Word> = None;
for (j, &pid) in slot_ptr.iter().enumerate() {
let raw = ctx.module.fresh_id();
newv.push(Instruction::new(
Op::Load,
Some(elem_ty),
Some(raw),
vec![Operand::IdRef(pid)],
));
let wide = ctx.module.fresh_id();
newv.push(Instruction::new(
Op::UConvert,
Some(result_pointee),
Some(wide),
vec![Operand::IdRef(raw)],
));
let shifted = if j == 0 {
wide
} else {
let sh = ctx.const_uint(j as u32 * slot_w);
let sid = ctx.module.fresh_id();
newv.push(Instruction::new(
Op::ShiftLeftLogical,
Some(result_pointee),
Some(sid),
vec![Operand::IdRef(wide), Operand::IdRef(sh)],
));
sid
};
acc = Some(match acc {
None => shifted,
Some(prev) => {
let last = j as u32 == ratio - 1;
let oid = if last { res } else { ctx.module.fresh_id() };
newv.push(Instruction::new(
Op::BitwiseOr,
Some(result_pointee),
Some(oid),
vec![Operand::IdRef(prev), Operand::IdRef(shifted)],
));
oid
}
});
}
let _ = acc;
} else {
let obj = match inst.operands.get(1) {
Some(Operand::IdRef(o)) => *o,
_ => {
return Err(
"store through reinterpret chain lost its object".to_string()
)
}
};
for (j, &pid) in slot_ptr.iter().enumerate() {
let shifted = if j == 0 {
obj
} else {
let sh = ctx.const_uint(j as u32 * slot_w);
let sid = ctx.module.fresh_id();
newv.push(Instruction::new(
Op::ShiftRightLogical,
Some(result_pointee),
Some(sid),
vec![Operand::IdRef(obj), Operand::IdRef(sh)],
));
sid
};
let narrow = ctx.module.fresh_id();
newv.push(Instruction::new(
Op::UConvert,
Some(elem_ty),
Some(narrow),
vec![Operand::IdRef(shifted)],
));
newv.push(Instruction::new(
Op::Store,
None,
None,
vec![Operand::IdRef(pid), Operand::IdRef(narrow)],
));
}
}
continue;
}
newv.push(inst);
}
ctx.module.functions[entry_idx].blocks[bi].instructions = newv;
}
Ok(())
}