use super::*;
use crate::spirv_module::{load_bytes, Module};
fn module_bytes(module: &Module) -> Vec<u8> {
module
.assemble()
.iter()
.flat_map(|word| word.to_le_bytes())
.collect()
}
fn prune_constant_branches_candidate(spv: &[u8]) -> Option<Module> {
let mut module = load_bytes(spv).ok()?;
native::prune_constant_branches_module(&mut module).ok()?;
Some(module)
}
fn relooper_candidate(spv: &[u8]) -> Option<Vec<u8>> {
let mut module = load_bytes(spv).ok()?;
native::rewrite_to_relooper_module(&mut module).ok()?;
Some(module_bytes(&module))
}
pub(crate) fn primary_wide_raw_store_guard_cfg_chain_if_needed(
validation_error: &str,
spv: &[u8],
module: &Module,
retry: &retry::RetryCtx<'_>,
) -> Option<Vec<u8>> {
if native::classify_validation_error(validation_error)
!= native::ValidationClass::CfgStructurization
|| !native::module_has_wide_raw_store_guard(module)
{
return None;
}
retry
.census("val-cfg:relooper", retry.relooper_retry(spv))
.or_else(|| {
retry.census(
"val-cfg:prune_then_relooper",
retry.prune_then_relooper(spv),
)
})
.or_else(|| retry.census("val-cfg:relooper_huge", retry.relooper_retry_huge(spv)))
.or_else(|| {
retry.census(
"val-cfg:relooper_then_value_select",
retry.relooper_then_value_select(spv),
)
})
.or_else(|| retry.census("val-cfg:raw_then_relooper", retry.raw_then_relooper()))
}
pub(super) fn emit_finish_primary_module(
san_ll: &str,
stage: passes::Stage,
frag: Option<&meta::FragMeta>,
vert: Option<&meta::VertMeta>,
kern: Option<&meta::KernMeta>,
entry_name: Option<&str>,
options: passes::TransformOptions,
) -> Result<FinishedModule, String> {
let structured = tools::llc_vulkan_spirv_with_sidecar(
san_ll,
Path::new(""),
kern,
entry_name,
stage_buffer_layouts(stage, frag, vert, kern),
)
.and_then(|b| {
finish_module(
b,
stage,
frag,
vert,
kern,
entry_name,
options,
FinishRewrites::Primary,
)
})?;
Ok(structured)
}
pub(crate) fn apply_xbind_phi_psb(module: &Module, spv: &[u8]) -> Vec<u8> {
let mut candidate = module.clone();
if native::rewrite_cross_binding_pointer_phis_module(&mut candidate).is_err() {
return spv.to_vec();
}
candidate
.assemble()
.iter()
.flat_map(|word| word.to_le_bytes())
.collect()
}
pub(crate) fn apply_primary_emit_rewrites_module(
module: &mut Module,
retained_global_ids: &mut [spirv::Word],
) {
if native::value_lower_cross_binding_pointer_merges_module(module) {
passes::canonicalize_ids_and_remap(module, retained_global_ids);
}
apply_agx_fc_prune_module(module, retained_global_ids);
}
pub(crate) fn primary_xbind_phi_psb_for_validation_error(
module: &Module,
spv: &[u8],
validation_error: &str,
tmp: &Path,
) -> Option<Vec<u8>> {
if native::classify_validation_error(validation_error)
!= native::ValidationClass::CrossBindingPointerMerge
{
return None;
}
if !native::has_cross_binding_pointer_phi_module(module) {
return None;
}
let primary = apply_xbind_phi_psb(module, spv);
(primary != spv && tools::spirv_val_bytes(&primary, tmp).is_ok()).then_some(primary)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn primary_primitive_phi_metadata_if_needed(
validation_error: &str,
san_ll: &str,
stage: passes::Stage,
frag: Option<&meta::FragMeta>,
vert: Option<&meta::VertMeta>,
kern: Option<&meta::KernMeta>,
entry_name: Option<&str>,
tmp: &Path,
options: passes::TransformOptions,
) -> Option<Vec<u8>> {
if native::classify_validation_error(validation_error) != native::ValidationClass::PointerTyping
{
return None;
}
let emitted = native::emit_vulkan_spirv_with_primitive_phi_metadata_sidecar(
san_ll,
kern,
entry_name,
stage_buffer_layouts(stage, frag, vert, kern),
)
.ok()?;
let finished = finish_module(
emitted,
stage,
frag,
vert,
kern,
entry_name,
options,
FinishRewrites::Primary,
)
.ok()?;
let primary_module = finished.module;
let primary = finished.bytes;
match tools::spirv_val_bytes(&primary, tmp) {
Ok(()) => Some(primary),
Err(error) => {
primary_xbind_phi_psb_for_validation_error(&primary_module, &primary, &error, tmp)
}
}
}
pub(crate) fn primary_pointer_typing_raw_if_needed(
validation_error: &str,
retry: &retry::RetryCtx<'_>,
) -> Option<Vec<u8>> {
if native::classify_validation_error(validation_error) != native::ValidationClass::PointerTyping
{
return None;
}
if validation_error.contains("reached non-composite") {
if let Some(bytes) = retry.fc_promote_logical() {
return Some(bytes);
}
}
retry.raw_retry()
}
pub(crate) fn primary_pointer_typing_wide_raw_guard_chain_if_needed(
validation_error: &str,
spv: &[u8],
module: &Module,
retry: &retry::RetryCtx<'_>,
) -> Option<Vec<u8>> {
if native::classify_validation_error(validation_error) != native::ValidationClass::PointerTyping
|| !retry.raw_reemit_has_wide_raw_store_guard()
{
return None;
}
retry
.census("val-ptr:prune", retry.prune_retry(spv))
.or_else(|| retry.census("val-ptr:subword_pack", retry.subword_pack_retry(module)))
.or_else(|| {
retry.census(
"val-ptr:prune_then_relooper",
retry.prune_then_relooper(spv),
)
})
.or_else(|| retry.raw_then_psb_chain("val-ptr:raw_then_relooper", "val-ptr:raw_psb"))
}
pub(crate) fn primary_other_prune_if_needed(
validation_error: &str,
spv: &[u8],
tmp: &Path,
) -> Option<Vec<u8>> {
if native::classify_validation_error(validation_error) != native::ValidationClass::Other {
return None;
}
let mut pruned_module = prune_constant_branches_candidate(spv)?;
let pruned = module_bytes(&pruned_module);
if pruned == spv {
return None;
}
match tools::spirv_val_bytes(&pruned, tmp) {
Ok(()) => return Some(pruned),
Err(error)
if native::classify_validation_error(&error)
!= native::ValidationClass::CfgStructurization =>
{
return None;
}
Err(_) => {}
}
native::rewrite_to_relooper_module_capped(
&mut pruned_module,
native::PRUNE_THEN_RELOOPER_MAX_BLOCKS,
)
.ok()?;
let primary = module_bytes(&pruned_module);
(primary != spv && tools::spirv_val_bytes(&primary, tmp).is_ok()).then_some(primary)
}
pub(crate) fn primary_relooper_if_needed(
validation_error: &str,
spv: &[u8],
tmp: &Path,
) -> Option<Vec<u8>> {
if native::classify_validation_error(validation_error)
!= native::ValidationClass::CfgStructurization
{
return None;
}
let candidate = relooper_candidate(spv)?;
(candidate != spv && tools::spirv_val_bytes(&candidate, tmp).is_ok()).then_some(candidate)
}
pub(crate) fn primary_cfg_raw_then_relooper_if_needed(
validation_error: &str,
spv: &[u8],
retry: &retry::RetryCtx<'_>,
) -> Option<Vec<u8>> {
if native::classify_validation_error(validation_error)
!= native::ValidationClass::CfgStructurization
{
return None;
}
retry
.census(
"val-cfg:relooper_then_value_select",
retry.relooper_then_value_select(spv),
)
.or_else(|| retry.census("val-cfg:raw_then_relooper", retry.raw_then_relooper()))
}
pub(crate) fn primary_cfg_prune_then_relooper_if_needed(
validation_error: &str,
spv: &[u8],
tmp: &Path,
) -> Option<Vec<u8>> {
if native::classify_validation_error(validation_error)
!= native::ValidationClass::CfgStructurization
{
return None;
}
if relooper_candidate(spv)
.is_some_and(|candidate| tools::spirv_val_bytes(&candidate, tmp).is_ok())
{
return None;
}
let mut pruned_module = prune_constant_branches_candidate(spv)?;
let pruned = module_bytes(&pruned_module);
if pruned == spv {
return None;
}
native::rewrite_to_relooper_module_capped(
&mut pruned_module,
native::PRUNE_THEN_RELOOPER_MAX_BLOCKS,
)
.ok()?;
let primary = module_bytes(&pruned_module);
(primary != spv && tools::spirv_val_bytes(&primary, tmp).is_ok()).then_some(primary)
}
pub(crate) fn primary_construct_tree_if_needed(
validation_error: &str,
retry: &retry::RetryCtx<'_>,
) -> Option<Vec<u8>> {
if native::classify_validation_error(validation_error)
!= native::ValidationClass::CfgStructurization
{
return None;
}
retry.census("val-cfg:construct_tree", retry.construct_tree_retry())
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn primary_dynamic_struct_index_inline_sroa_relooper_if_needed(
validation_error: &str,
san_ll: &str,
stage: passes::Stage,
frag: Option<&meta::FragMeta>,
vert: Option<&meta::VertMeta>,
kern: Option<&meta::KernMeta>,
entry_name: Option<&str>,
tmp: &Path,
options: passes::TransformOptions,
) -> Option<Vec<u8>> {
if native::classify_validation_error(validation_error) != native::ValidationClass::Other
|| !native::is_dynamic_struct_index_error(validation_error)
{
return None;
}
let inlined = tools::llc_vulkan_spirv_inline_sroa_with_sidecar(
san_ll,
tmp,
kern,
entry_name,
stage_buffer_layouts(stage, frag, vert, kern),
)
.and_then(|spv| {
finish_module(
spv,
stage,
frag,
vert,
kern,
entry_name,
options,
FinishRewrites::Plain,
)
})
.map(|finished| finished.bytes)
.ok()?;
let inline_error = tools::spirv_val_bytes(&inlined, tmp).err()?;
if native::classify_validation_error(&inline_error)
!= native::ValidationClass::CfgStructurization
{
return None;
}
let primary = relooper_candidate(&inlined)?;
tools::spirv_val_bytes(&primary, tmp)
.is_ok()
.then_some(primary)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn primary_logical_pointer_inline_sroa_raw_if_needed(
validation_error: &str,
san_ll: &str,
stage: passes::Stage,
frag: Option<&meta::FragMeta>,
vert: Option<&meta::VertMeta>,
kern: Option<&meta::KernMeta>,
entry_name: Option<&str>,
tmp: &Path,
options: passes::TransformOptions,
) -> Option<Vec<u8>> {
if native::classify_validation_error(validation_error) != native::ValidationClass::Other
|| !native::is_logical_pointer_operand_error(validation_error)
{
return None;
}
let primary = tools::llc_vulkan_spirv_inline_sroa_raw_with_sidecar(
san_ll,
tmp,
kern,
entry_name,
stage_buffer_layouts(stage, frag, vert, kern),
)
.and_then(|spv| {
finish_module(
spv,
stage,
frag,
vert,
kern,
entry_name,
options,
FinishRewrites::Plain,
)
})
.map(|finished| finished.bytes)
.ok()?;
tools::spirv_val_bytes(&primary, tmp)
.is_ok()
.then_some(primary)
}
pub(crate) fn primary_opaque_image_select_module_if_needed(
validation_error: &str,
module: &Module,
spv: &[u8],
tmp: &Path,
) -> Option<Vec<u8>> {
if native::classify_validation_error(validation_error) != native::ValidationClass::Other {
return None;
}
let mut primary_module = module.clone();
native::rewrite_opaque_image_selects_module(&mut primary_module).ok()?;
let primary = primary_module
.assemble()
.iter()
.flat_map(|word| word.to_le_bytes())
.collect::<Vec<_>>();
(primary != spv && tools::spirv_val_bytes(&primary, tmp).is_ok()).then_some(primary)
}
fn apply_agx_fc_prune_module(module: &mut Module, retained_global_ids: &[spirv::Word]) {
if !native::has_bodiless_agx_call_module(module) {
return;
}
let original = module.clone();
let changed = native::prune_constant_branches_module_preserving(module, retained_global_ids);
if !changed || native::has_bodiless_agx_call_module(module) {
*module = original;
} else {
native::close_module_capabilities(module);
}
}