struct LoopFinder<'a> {
cfg: &'a ControlFlowGraph,
loop_header_to_exit_targets: IndexMap<ControlRegion, IndexSet<ControlRegion, BuildHasherDefault<FxHasher>>, BuildHasherDefault<FxHasher>>,
scc_stack: Vec<ControlRegion>,
scc_state: EntityOrientedDenseMap<ControlRegion, SccState>,
}
Expand description
Minimal loop analysis, based on Tarjan’s SCC (strongly connected components) algorithm, applied recursively (for every level of loop nesting).
Here “minimal” means that each loops is the smallest CFG subgraph possible (excluding any control-flow paths that cannot reach a backedge and cycle), i.e. each loop is a CFG SCC (strongly connected component).
These “minimal loops” contrast with the “maximal loops” that the greedy
architecture of the structurizer would naively produce, with the main impact
of the difference being where loop exits (break
s) “merge” (or “reconverge”),
which SPIR-V encodes via OpLoopMerge
, and is significant for almost anything
where shared memory and/or subgroup ops can allow observing when invocations
“wait for others in the subgroup to exit the loop” (or when they fail to wait).
This analysis was added to because of two observations wrt “reconvergence”:
- syntactic loops (from some high-level language), when truly structured
(i.e. only using
while
/do
-while
exit conditions, notbreak
etc.), always map to “minimal loops” on a CFG, as the only loop exit edge is built-in, and no part of the syntactic “loop body” can be its successor - more pragmatically, compiling shader languages to SPIR-V seems to (almost?)
always either fully preserve syntactic loops (via SPIR-V
OpLoopMerge
), or structurize CFGs in a way that produces “minimal loops”, which can be misleading with explicitbreak
s (moving user code from just before thebreak
to after the loop), but is less impactful than “maximal loops”
Fields§
§cfg: &'a ControlFlowGraph
§loop_header_to_exit_targets: IndexMap<ControlRegion, IndexSet<ControlRegion, BuildHasherDefault<FxHasher>>, BuildHasherDefault<FxHasher>>
§scc_stack: Vec<ControlRegion>
SCC accumulation stack, where CFG nodes collect during the depth-first traversal, and are only popped when their “SCC root” (loop header) is (note that multiple SCCs on the stack does not indicate SCC nesting, but rather a path between two SCCs, i.e. a loop following another).
scc_state: EntityOrientedDenseMap<ControlRegion, SccState>
Per-CFG-node traversal state (often just pointing to a scc_stack
slot).
Implementations§
source§impl<'a> LoopFinder<'a>
impl<'a> LoopFinder<'a>
fn new(cfg: &'a ControlFlowGraph) -> Self
sourcefn find_earliest_scc_root_of(
&mut self,
node: ControlRegion,
) -> (Option<SccStackIdx>, EventualCfgExits)
fn find_earliest_scc_root_of( &mut self, node: ControlRegion, ) -> (Option<SccStackIdx>, EventualCfgExits)
Tarjan’s SCC algorithm works by computing the “earliest” reachable node,
from every node (often using the name lowlink
), which will be equal
to the origin node itself iff that node is an “SCC root” (loop header),
and always point to an “earlier” node if a cycle (via loop backedge) was
found from somewhere else in the SCC (i.e. from inside the loop body).
Here we track stack indices (as the stack order is the traversal order), and distinguish the acyclic case to avoid treating most nodes as self-loops.
Auto Trait Implementations§
impl<'a> Freeze for LoopFinder<'a>
impl<'a> RefUnwindSafe for LoopFinder<'a>
impl<'a> Send for LoopFinder<'a>
impl<'a> Sync for LoopFinder<'a>
impl<'a> Unpin for LoopFinder<'a>
impl<'a> UnwindSafe for LoopFinder<'a>
Blanket Implementations§
source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
source§impl<T> IntoEither for T
impl<T> IntoEither for T
source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self
into a Left
variant of Either<Self, Self>
if into_left
is true
.
Converts self
into a Right
variant of Either<Self, Self>
otherwise. Read moresource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self
into a Left
variant of Either<Self, Self>
if into_left(&self)
returns true
.
Converts self
into a Right
variant of Either<Self, Self>
otherwise. Read more