spirt

Struct ControlRegion

source
pub struct ControlRegion(/* private fields */);
Expand description

Entity handle for a ControlRegionDef (a control-flow region).

A ControlRegion (“control-flow region”) is a linear chain of ControlNodes, describing a single-entry single-exit (SESE) control-flow “region” (subgraph) in a function’s control-flow graph (CFG).

§Control-flow

In SPIR-T, two forms of control-flow are used:

  • “structured”: ControlRegions and ControlNodes in a “mutual tree”
    • i.e. each such ControlRegion can only appear in exactly one ControlNode, and each ControlNode can only appear in exactly one ControlRegion
    • a region is either the function’s body, or used as part of ControlNode (e.g. the “then” case of an if-else), itself part of a larger region
    • when inside a region, reaching any other part of the function (or any other function on call stack) requires leaving through the region’s single exit (also called “merge”) point, i.e. its execution is either:
      • “convergent”: the region completes and continues into its parent ControlNode, or function (the latter being a “structured return”)
      • “divergent”: execution gets stuck in the region (an infinite loop), or is aborted (e.g. OpTerminateInvocation from SPIR-V)
  • “unstructured”: ControlRegions which connect to other ControlRegions using cfg::ControlInsts (as described by a cfg::ControlFlowGraph)

When a function’s entire body can be described by a single ControlRegion, that function is said to have (entirely) “structured control-flow”.

Mixing “structured” and “unstructured” control-flow is supported because:

  • during structurization, it allows structured subgraphs to remain connected by the same CFG edges that were connecting smaller ControlRegions before
  • structurization doesn’t have to fail in the cases it doesn’t fully support yet, but can instead result in a “maximally structured” function

Other IRs may use different “structured control-flow” definitions, notably:

  • SPIR-V uses a laxer definition, that corresponds more to the constraints of the GLSL language, and is single-entry multiple-exit (SEME) with “alternate exits” consisting of breaks out of switches and loops, and returns (making it non-trivial to inline one function into another)
  • RVSDG inspired SPIR-T’s design, but its regions are (acyclic) graphs, it makes no distinction between control-flow and “computational” nodes, and its execution order is determined by value/state dependencies alone (SPIR-T may get closer to it in the future, but the initial compromise was chosen to limit the effort of lowering/lifting from/to SPIR-V)

§Data-flow interactions

SPIR-T Values follow “single static assignment” (SSA), just like SPIR-V:

  • inside a function, any new value is produced (or “defined”) as an output of DataInst/ControlNode, and “uses” of that value are Values variants which refer to the defining DataInst/ControlNode directly (guaranteeing the “single” and “static” of “SSA”, by construction)
  • the definition of a value must “dominate” all of its uses (i.e. in all possible execution paths, the definition precedes all uses)

But unlike SPIR-V, SPIR-T’s structured control-flow has implications for SSA:

  • dominance is simpler, so values defined in a ControlRegion can be used:
    • later in that region, including in the region’s outputs (which allows “exporting” values out to the rest of the function)
    • outside that region, but only if the parent ControlNode is a Loop (that is, when the region is a loop’s body)
      • this is an “emergent” property, stemming from the region having to execute (at least once) before the parent ControlNode can complete, but is not is not ideal and should eventually be replaced with passing all such values through loop (body) outputs
  • instead of φ (“phi”) nodes, SPIR-T uses region outputs to merge values coming from separate control-flow paths (i.e. the cases of a Select), and region inputs for passing values back along loop backedges (additionally, the body’s inputs are used for function parameters)
    • like the “block arguments” alternative to SSA phi nodes (which some other SSA IRs use), this has the advantage of keeping the uses of the “source” values in their respective paths (where they’re dominated), instead of in the merge (where phi nodes require special-casing, as their “uses” of all the “source” values would normally be illegal)
    • in unstructured control-flow, region inputs are additionally used for representing phi nodes, as cfg::ControlInsts passing values to their target regions
      • all value uses across unstructured control-flow edges (i.e. not in the same region containing the value definition) require explicit passing, as unstructured control-flow ControlRegions do not themselves get any implied dominance relations from the shape of the control-flow graph (unlike most typical CFG+SSA IRs)

Trait Implementations§

source§

impl Clone for ControlRegion

source§

fn clone(&self) -> ControlRegion

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Entity for ControlRegion

source§

impl Hash for ControlRegion

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq for ControlRegion

source§

fn eq(&self, other: &ControlRegion) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
source§

impl Copy for ControlRegion

source§

impl Eq for ControlRegion

source§

impl StructuralPartialEq for ControlRegion

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> CloneToUninit for T
where T: Clone,

source§

unsafe fn clone_to_uninit(&self, dst: *mut T)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dst. Read more
source§

impl<E, V> EntityOrientedMapKey<V> for E
where E: Entity,

source§

type Entity = E

The entity type that appears exactly once in every value of Self.
source§

type DenseValueSlots = Option<V>

A type holding enough different Option<V> slots, for all possible values of Self, for a given Self::Entity value contained inside.
source§

fn to_entity(key: E) -> E

source§

fn get_dense_value_slot(_: E, slot: &Option<V>) -> &Option<V>

source§

fn get_dense_value_slot_mut(_: E, slot: &mut Option<V>) -> &mut Option<V>

source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> IntoEither for T

source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
source§

impl<T> ToOwned for T
where T: Clone,

source§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

source§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.