use super::*;
pub(in crate::native) const SPLIT_PREFIX: &str = "%metal2vulkan.lmerge.";
pub(in crate::native) const TEXITRET_TOKEN: &str = "texitret";
pub(in crate::native) const TLOOPRET_TOKEN: &str = "tloopret";
pub(in crate::native) const SEL_TOKEN: &str = "sel";
pub(in crate::native) const CONT_PREFIX: &str = "%metal2vulkan.cont.";
pub(in crate::native) const EXIT_EDGE_PREFIX: &str = "%metal2vulkan.exitedge.";
pub(in crate::native) const EXIT_SEL_PREFIX: &str = "%metal2vulkan.exitsel.";
pub(in crate::native) fn is_terminal_exit_return_name(name: &str) -> bool {
name.strip_prefix(SPLIT_PREFIX)
.is_some_and(|rest| rest.starts_with(TEXITRET_TOKEN))
}
pub(in crate::native) fn role_for_name(name: &str) -> BlockRole {
if is_terminal_exit_return_name(name) {
BlockRole::TerminalExitReturn
} else if name.starts_with(SPLIT_PREFIX) {
BlockRole::LMerge
} else if name.starts_with(super::super::blocks::SWITCH_BYPASS_PREFIX) {
BlockRole::SwitchBypass
} else {
BlockRole::Normal
}
}
pub(in crate::native) struct StructuredPlan {
pub(in crate::native) blocks: Vec<BodyBlock>,
pub(in crate::native) loop_merges: HashMap<String, LoopMergeInfo>,
pub(in crate::native) branch_merges: HashMap<(String, String), String>,
pub(in crate::native) branch_merges_by_header: HashMap<String, String>,
pub(in crate::native) switch_merges: HashMap<String, String>,
}
pub(in crate::native) const CROSS_ARM_EDGE_MAX_BLOCKS: usize = 300;
pub(in crate::native) const LOOP_EXIT_SELECTION_MAX_BLOCKS: usize = 300;
pub(in crate::native) fn blocks_changed(before: &[BodyBlock], after: &[BodyBlock]) -> bool {
before.len() != after.len()
|| before
.iter()
.zip(after)
.any(|(left, right)| left.name != right.name || block_body_changed(left, right))
}
fn block_body_changed(left: &BodyBlock, right: &BodyBlock) -> bool {
match (&left.typed, &right.typed) {
(Some(l), Some(r)) => format!("{l:?}") != format!("{r:?}"),
(None, None) => false,
_ => true,
}
}
pub(in crate::native) fn structured_plan(blocks: &[BodyBlock]) -> Option<StructuredPlan> {
let switch_private = super::clone_crossarm::privatize_switch_case_continuations(blocks);
let deep_shared = super::clone_crossarm::privatize_deep_shared_continuations(&switch_private);
if deep_shared.len() != blocks.len() {
if let Some(plan) = structured_plan_ladder(&deep_shared, false) {
return Some(plan);
}
if let Some(plan) = structured_plan_ladder(&deep_shared, true) {
return Some(plan);
}
}
if let Some(plan) = structured_plan_ladder(blocks, false) {
return Some(plan);
}
if let Some(plan) = structured_plan_ladder(blocks, true) {
return Some(plan);
}
if let Some(plan) = structured_plan_divergent_exit(blocks) {
return Some(plan);
}
None
}
pub(in crate::native) fn structured_plan_divergent_exit(
blocks: &[BodyBlock],
) -> Option<StructuredPlan> {
let single_exit = super::clone_crossarm::separate_divergent_selection_exits(blocks)?;
let shared_exit = super::clone_crossarm::privatize_shared_phi_exit_predecessors(&single_exit);
if !blocks_changed(&single_exit, &shared_exit) {
return None;
}
for allow_bare_exit in [false, true] {
if let Some(plan) = structured_plan_ladder(&shared_exit, allow_bare_exit) {
return Some(plan);
}
}
None
}
pub(in crate::native) fn structured_plan_ladder(
blocks: &[BodyBlock],
allow_bare_exit: bool,
) -> Option<StructuredPlan> {
let force_converge = crate::env_vars::converge_inloop();
if let Some(plan) =
structured_plan_inner4(blocks, force_converge, false, false, allow_bare_exit)
{
return Some(plan);
}
let privatized = super::clone_crossarm::privatize_trivial_cross_arm(blocks);
if privatized.len() != blocks.len() {
if let Some(plan) =
structured_plan_inner4(&privatized, force_converge, false, false, allow_bare_exit)
{
return Some(plan);
}
}
let region = super::clone_crossarm::privatize_region_cross_arm(blocks);
if region.len() != blocks.len() {
if let Some(plan) =
structured_plan_inner4(®ion, force_converge, false, false, allow_bare_exit)
{
return Some(plan);
}
}
if let Some(plan) = structured_plan_inner4(blocks, true, false, false, allow_bare_exit) {
return Some(plan);
}
if blocks.len() <= LOOP_EXIT_SELECTION_MAX_BLOCKS {
let lowered_switches = super::blocks::lower_loop_exit_switches(blocks);
if blocks_changed(blocks, &lowered_switches) {
if let Some(plan) =
structured_plan_inner5(&lowered_switches, true, false, false, allow_bare_exit, true)
{
return Some(plan);
}
} else if let Some(plan) =
structured_plan_inner5(blocks, true, false, false, allow_bare_exit, true)
{
return Some(plan);
}
}
if blocks.len() <= TERMINAL_EXIT_SELECTION_MAX_BLOCKS {
for &(converge, break_aware) in &[(false, false), (true, false), (true, true)] {
if let Some(plan) = structured_plan_inner6(
blocks,
converge,
break_aware,
false,
allow_bare_exit,
false,
true,
) {
return Some(plan);
}
}
}
if switch_gate_excludes(blocks) {
return None;
}
if let Some(plan) = structured_plan_inner4(blocks, true, true, false, allow_bare_exit) {
return Some(plan);
}
{
if let Some(destraddled) = restructure_straddle_loop_merges(blocks) {
if let Some(plan) =
structured_plan_inner4(&destraddled, force_converge, false, false, allow_bare_exit)
{
return Some(plan);
}
if let Some(plan) =
structured_plan_inner4(&destraddled, true, false, false, allow_bare_exit)
{
return Some(plan);
}
if let Some(plan) =
structured_plan_inner4(&destraddled, true, true, false, allow_bare_exit)
{
return Some(plan);
}
}
}
{
if let Some(destraddled) = restructure_straddle_loop_merges_with(blocks, true, true) {
if let Some(plan) =
structured_plan_inner4(&destraddled, true, false, false, allow_bare_exit)
{
return Some(plan);
}
if let Some(plan) =
structured_plan_inner4(&destraddled, true, true, false, allow_bare_exit)
{
return Some(plan);
}
let region2 = super::clone_crossarm::privatize_region_cross_arm(&destraddled);
if region2.len() != destraddled.len() {
if let Some(plan) =
structured_plan_inner4(®ion2, true, false, false, allow_bare_exit)
{
return Some(plan);
}
if let Some(plan) =
structured_plan_inner4(®ion2, true, true, false, allow_bare_exit)
{
return Some(plan);
}
}
}
}
if region.len() != blocks.len() {
if let Some(plan) = structured_plan_inner4(®ion, true, false, false, allow_bare_exit) {
return Some(plan);
}
if let Some(plan) = structured_plan_inner4(®ion, true, true, false, allow_bare_exit) {
return Some(plan);
}
}
if !switch_gate_excludes(blocks) {
for &(bl, cv, br) in &[
(blocks, force_converge, false),
(blocks, true, false),
(blocks, true, true),
] {
if let Some(plan) = structured_plan_inner4(bl, cv, br, true, allow_bare_exit) {
return Some(plan);
}
}
if region.len() != blocks.len() {
for &(cv, br) in &[(force_converge, false), (true, false), (true, true)] {
if let Some(plan) = structured_plan_inner4(®ion, cv, br, true, allow_bare_exit) {
return Some(plan);
}
}
}
}
if blocks.len() <= CROSS_ARM_EDGE_MAX_BLOCKS {
let cloned = super::clone_crossarm::privatize_cross_arm_edge(blocks);
if cloned.len() != blocks.len() {
for &(cv, br) in &[(force_converge, false), (true, false), (true, true)] {
if let Some(plan) = structured_plan_inner4(&cloned, cv, br, false, allow_bare_exit)
{
return Some(plan);
}
}
}
}
if blocks.len() <= CROSS_ARM_EDGE_MAX_BLOCKS {
for &(dcv, dbr) in &[(true, true), (force_converge, false), (true, false)] {
let cloned = privatize_synthesized_cross_arm_shared(blocks, dcv, dbr);
if cloned.len() != blocks.len() {
for &(cv, br) in &[(force_converge, false), (true, false), (true, true)] {
if let Some(plan) =
structured_plan_inner4(&cloned, cv, br, false, allow_bare_exit)
{
return Some(plan);
}
}
}
}
}
None
}
pub(in crate::native) fn is_switch_block(b: &BodyBlock) -> bool {
b.typed.as_ref().is_some_and(|carrier| {
matches!(
carrier.terminator,
crate::native::tir::TirTerminator::Switch { .. }
)
})
}
pub(in crate::native) fn blocks_contain_multilevel_break_switch(blocks: &[BodyBlock]) -> bool {
let switch_succs: HashMap<&str, Vec<String>> = blocks
.iter()
.filter(|b| is_switch_block(b))
.map(|b| (b.name.as_str(), block_successors(b)))
.collect();
if switch_succs.is_empty() {
return false;
}
let forest = analyze(blocks);
for l in &forest.loops {
let body: HashSet<&str> = l.body.iter().map(String::as_str).collect();
for (name, succs) in &switch_succs {
if *name == l.header.as_str() || !body.contains(name) {
continue;
}
if succs.iter().any(|s| !body.contains(s.as_str())) {
return true;
}
}
}
false
}
pub(in crate::native) fn switch_gate_excludes(blocks: &[BodyBlock]) -> bool {
blocks_contain_multilevel_break_switch(blocks)
}