use super::*;
pub(crate) fn module_has_wide_raw_store_guard(module: &Module) -> bool {
let defs: HashMap<Word, &Instruction> = module
.all_inst_iter()
.filter_map(|inst| inst.result_id.map(|id| (id, inst)))
.collect();
let is_u64_constant = |id: Word| {
let Some(inst) = defs.get(&id) else {
return false;
};
if inst.class.opcode != Op::Constant {
return false;
}
let Some(ty) = inst.result_type else {
return false;
};
let Some(type_inst) = defs.get(&ty) else {
return false;
};
type_inst.class.opcode == Op::TypeInt
&& type_inst.operands.first() == Some(&Operand::LiteralBit32(64))
&& type_inst.operands.get(1) == Some(&Operand::LiteralBit32(0))
};
for function in &module.functions {
let blocks: HashMap<Word, &crate::spirv_module::Block> = function
.blocks
.iter()
.filter_map(|block| {
block
.label
.as_ref()
.and_then(|label| label.result_id)
.map(|id| (id, block))
})
.collect();
for block in &function.blocks {
for (index, branch) in block.instructions.iter().enumerate() {
if branch.class.opcode != Op::BranchConditional || index == 0 {
continue;
}
let (
Some(Operand::IdRef(condition)),
Some(Operand::IdRef(write_label)),
Some(Operand::IdRef(false_label)),
) = (
branch.operands.first(),
branch.operands.get(1),
branch.operands.get(2),
)
else {
continue;
};
let merge = &block.instructions[index - 1];
if merge.class.opcode != Op::SelectionMerge {
continue;
}
let Some(Operand::IdRef(merge_label)) = merge.operands.first() else {
continue;
};
if false_label != merge_label {
continue;
}
let Some(compare) = defs.get(condition) else {
continue;
};
if compare.class.opcode != Op::ULessThanEqual
|| !matches!(compare.operands.get(1), Some(Operand::IdRef(max)) if is_u64_constant(*max))
{
continue;
}
let Some(write_block) = blocks.get(write_label) else {
continue;
};
let writes_then_merges = write_block
.instructions
.iter()
.any(|inst| is_spirv_memory_write(inst.class.opcode))
&& write_block.instructions.last().is_some_and(|terminator| {
terminator.class.opcode == Op::Branch
&& terminator.operands.first() == Some(&Operand::IdRef(*merge_label))
});
if writes_then_merges {
return true;
}
}
}
}
false
}
fn is_spirv_memory_write(op: Op) -> bool {
matches!(
op,
Op::Store
| Op::AtomicStore
| Op::AtomicExchange
| Op::AtomicCompareExchange
| Op::AtomicCompareExchangeWeak
| Op::AtomicIIncrement
| Op::AtomicIDecrement
| Op::AtomicIAdd
| Op::AtomicISub
| Op::AtomicSMin
| Op::AtomicUMin
| Op::AtomicSMax
| Op::AtomicUMax
| Op::AtomicAnd
| Op::AtomicOr
| Op::AtomicXor
| Op::AtomicFAddEXT
| Op::AtomicFMinEXT
| Op::AtomicFMaxEXT
)
}
pub(crate) fn rewrite_cross_binding_pointer_merges_module(
module: &mut Module,
) -> Result<(), String> {
if !psb::rewrite_cross_binding_pointer_merges(module) {
return Err("native emitter: no cross-binding pointer merge to rewrite".to_string());
}
add_native_module_capabilities(module);
Ok(())
}
pub(crate) fn has_cross_binding_pointer_phi_module(module: &Module) -> bool {
psb::has_cross_binding_pointer_phi(module)
}
pub(crate) fn rewrite_cross_binding_pointer_phis_module(module: &mut Module) -> Result<(), String> {
if !psb::rewrite_cross_binding_pointer_phis(module) {
return Err("native emitter: no cross-binding pointer phi to rewrite".to_string());
}
add_native_module_capabilities(module);
Ok(())
}
pub(crate) fn rewrite_cross_binding_pointer_merges_to_values_module(
module: &mut Module,
) -> Result<(), String> {
if !psb_value_select::rewrite_cross_binding_pointer_merges_to_values(module) {
return Err("native emitter: no cross-binding pointer merge to value-lower".to_string());
}
add_native_module_capabilities(module);
Ok(())
}
pub(crate) fn rewrite_opaque_image_selects_module(module: &mut Module) -> Result<(), String> {
if !opaque_image_select::rewrite_opaque_image_selects(module) {
return Err("native emitter: no lowerable opaque image select".to_string());
}
add_native_module_capabilities(module);
Ok(())
}
pub(crate) fn rewrite_workgroup_atomic_floats_module(module: &mut Module) -> Result<(), String> {
if !wg_atomic::rewrite_workgroup_atomic_floats(module) {
return Err("native emitter: no workgroup float-as-int atomic to remodel".to_string());
}
add_native_module_capabilities(module);
Ok(())
}
pub(crate) fn rewrite_subword_packed_scalars_module(module: &mut Module) -> Result<(), String> {
if !subword_pack::rewrite_subword_packed_scalars(module) {
return Err("native emitter: no sub-word packed scalar to remodel".to_string());
}
add_native_module_capabilities(module);
Ok(())
}
pub(crate) fn rewrite_logical_pointer_phis_retry_module(module: &mut Module) -> Result<(), String> {
if !phi_index::rewrite_logical_pointer_phis(module) {
return Err("native emitter: no logical pointer phi to rewrite".to_string());
}
add_native_module_capabilities(module);
Ok(())
}
pub(crate) fn rewrite_logical_pointer_phis_module(module: &mut Module) -> bool {
phi_index::rewrite_logical_pointer_phis(module)
}
pub(crate) fn rewrite_integer_width_phis_module(module: &mut Module) -> bool {
phi_index::rewrite_integer_width_phis(module)
}
pub(crate) fn demote_nondominating_values_module(module: &mut Module) -> bool {
cfg::demote_nondominating_values(module)
}
pub(crate) fn split_multientry_loop_selection_exits_module(module: &mut Module) -> bool {
cfg::split_multientry_loop_selection_exits(module)
}
pub(crate) fn value_lower_cross_binding_pointer_merges_module(module: &mut Module) -> bool {
let defs = module
.types_global_values
.iter()
.filter_map(|inst| inst.result_id.map(|id| (id, inst.clone())))
.collect::<HashMap<_, _>>();
let has_storage_buffer_pointer_merge = module
.functions
.iter()
.flat_map(|function| &function.blocks)
.flat_map(|block| &block.instructions)
.filter(|inst| matches!(inst.class.opcode, Op::Phi | Op::Select))
.filter_map(|inst| inst.result_type)
.any(|ty| ptr_storage(&defs, ty) == Some(StorageClass::StorageBuffer));
if !has_storage_buffer_pointer_merge {
return false;
}
psb_value_select::rewrite_cross_binding_pointer_merges_to_values(module)
}
pub(crate) fn rewrite_variable_pointer_phis_module(module: &mut Module) -> Result<(), String> {
if !phi_index::rewrite_variable_pointer_phis(module) {
return Err("native emitter: no variable pointer phi to rewrite".to_string());
}
add_native_module_capabilities(module);
Ok(())
}
pub(crate) fn prune_constant_branches_module(module: &mut Module) -> Result<(), String> {
if !constfold::prune_constant_branches(module) {
return Err("native emitter: no constant branch to prune".to_string());
}
add_native_module_capabilities(module);
Ok(())
}
pub(crate) fn prune_constant_branches_module_preserving(
module: &mut Module,
preserved_global_ids: &[Word],
) -> bool {
let roots = preserved_global_ids.iter().copied().collect();
constfold::prune_constant_branches_preserving(module, &roots)
}
pub(crate) fn has_bodiless_agx_call_module(module: &Module) -> bool {
use std::collections::HashSet;
let agx_names: HashSet<spirv::Word> = module
.debug_names
.iter()
.filter(|inst| inst.class.opcode == spirv::Op::Name)
.filter_map(|inst| {
let Operand::IdRef(id) = inst.operands.first()? else {
return None;
};
let Operand::LiteralString(name) = inst.operands.get(1)? else {
return None;
};
name.starts_with("llvm.agx").then_some(*id)
})
.collect();
if agx_names.is_empty() {
return false;
}
let bodiless: HashSet<spirv::Word> = module
.functions
.iter()
.filter(|f| f.blocks.is_empty())
.filter_map(|f| f.def.as_ref().and_then(|d| d.result_id))
.filter(|id| agx_names.contains(id))
.collect();
if bodiless.is_empty() {
return false;
}
module.functions.iter().any(|f| {
f.blocks.iter().any(|b| {
b.instructions.iter().any(|inst| {
inst.class.opcode == spirv::Op::FunctionCall
&& matches!(
inst.operands.first(),
Some(Operand::IdRef(callee)) if bodiless.contains(callee)
)
})
})
})
}
pub(crate) fn reconcile_whole_buffer_scalar_arms_module(module: &mut Module) -> Result<(), String> {
if !buffer_arm_reconcile::reconcile_whole_buffer_scalar_arms(module) {
return Err("native emitter: no whole-buffer scalar arm to reconcile".to_string());
}
add_native_module_capabilities(module);
Ok(())
}
pub(crate) fn rewrite_to_relooper_module(module: &mut Module) -> Result<(), String> {
rewrite_to_relooper_module_capped(module, relooper::default_max_relooper_blocks())
}
pub const PRUNE_THEN_RELOOPER_MAX_BLOCKS: usize = 2048;
pub const CFG_EMIT_RELOOPER_MAX_BLOCKS: usize = 2048;
pub(crate) fn rewrite_to_relooper_module_capped(
module: &mut Module,
max_blocks: usize,
) -> Result<(), String> {
if !relooper::rewrite_to_relooper(module, max_blocks) {
return Err("native emitter: no function to relooper".to_string());
}
add_native_module_capabilities(module);
Ok(())
}