Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ With the initial focus being on [Rust-GPU]'s usecase, various (otherwise desirab
* "entity" system for e.g. definitions in a module, instructions in a function, etc.
* disallows iteration in favor of/forcing the use of efficient indexing
* structured control-flow "regions" inspired by RVSDG, stricter than SPIR-V
(see `ControlRegionDef`'s docs for more details)
(see `RegionDef`'s docs for more details)

</td><td>

Expand Down
189 changes: 90 additions & 99 deletions src/cfg.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/cfgssa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//! be taken to preserve the correctness of such implicit dataflow across all
//! transformations, and it's overall far more fragile than the local dataflow
//! of e.g. phi nodes (or their alternative "block arguments"), or in SPIR-T's
//! case, `ControlRegion` inputs and `ControlNode` outputs (inspired by RVSDG,
//! case, `Region` inputs and `ControlNode` outputs (inspired by RVSDG,
//! which has even stricter isolation/locality in its regions).

use crate::{FxIndexMap, FxIndexSet};
Expand Down
2 changes: 1 addition & 1 deletion src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -962,7 +962,7 @@ macro_rules! entities {
entities! {
GlobalVar => chunk_size(0x1_0000) crate::GlobalVarDecl,
Func => chunk_size(0x1_0000) crate::FuncDecl,
ControlRegion => chunk_size(0x1000) crate::ControlRegionDef,
Region => chunk_size(0x1000) crate::RegionDef,
ControlNode => chunk_size(0x1000) EntityListNode<ControlNode, crate::ControlNodeDef>,
DataInst => chunk_size(0x1000) EntityListNode<DataInst, crate::DataInstDef>,
}
40 changes: 20 additions & 20 deletions src/func_at.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
#![allow(clippy::should_implement_trait)]

use crate::{
Context, ControlNode, ControlNodeDef, ControlRegion, ControlRegionDef, DataInst, DataInstDef,
EntityDefs, EntityList, EntityListIter, FuncDefBody, Type, Value,
Context, ControlNode, ControlNodeDef, DataInst, DataInstDef, EntityDefs, EntityList,
EntityListIter, FuncDefBody, Region, RegionDef, Type, Value,
};

/// Immutable traversal (i.e. visiting) helper for intra-function entities.
Expand All @@ -22,7 +22,7 @@ use crate::{
/// (e.g. [`EntityList<ControlNode>`]).
#[derive(Copy, Clone)]
pub struct FuncAt<'a, P: Copy> {
pub control_regions: &'a EntityDefs<ControlRegion>,
pub regions: &'a EntityDefs<Region>,
pub control_nodes: &'a EntityDefs<ControlNode>,
pub data_insts: &'a EntityDefs<DataInst>,

Expand All @@ -33,17 +33,17 @@ impl<'a, P: Copy> FuncAt<'a, P> {
/// Reposition to `new_position`.
pub fn at<P2: Copy>(self, new_position: P2) -> FuncAt<'a, P2> {
FuncAt {
control_regions: self.control_regions,
regions: self.regions,
control_nodes: self.control_nodes,
data_insts: self.data_insts,
position: new_position,
}
}
}

impl<'a> FuncAt<'a, ControlRegion> {
pub fn def(self) -> &'a ControlRegionDef {
&self.control_regions[self.position]
impl<'a> FuncAt<'a, Region> {
pub fn def(self) -> &'a RegionDef {
&self.regions[self.position]
}

pub fn at_children(self) -> FuncAt<'a, EntityList<ControlNode>> {
Expand Down Expand Up @@ -110,7 +110,7 @@ impl FuncAt<'_, Value> {
pub fn type_of(self, cx: &Context) -> Type {
match self.position {
Value::Const(ct) => cx[ct].ty,
Value::ControlRegionInput { region, input_idx } => {
Value::RegionInput { region, input_idx } => {
self.at(region).def().inputs[input_idx as usize].ty
}
Value::ControlNodeOutput { control_node, output_idx } => {
Expand All @@ -126,7 +126,7 @@ impl FuncAt<'_, Value> {
/// The point/position type `P` should be an entity or a shallow entity wrapper
/// (e.g. [`EntityList<ControlNode>`]).
pub struct FuncAtMut<'a, P: Copy> {
pub control_regions: &'a mut EntityDefs<ControlRegion>,
pub regions: &'a mut EntityDefs<Region>,
pub control_nodes: &'a mut EntityDefs<ControlNode>,
pub data_insts: &'a mut EntityDefs<DataInst>,

Expand All @@ -137,7 +137,7 @@ impl<'a, P: Copy> FuncAtMut<'a, P> {
/// Emulate a "reborrow", which is automatic only for `&mut` types.
pub fn reborrow(&mut self) -> FuncAtMut<'_, P> {
FuncAtMut {
control_regions: self.control_regions,
regions: self.regions,
control_nodes: self.control_nodes,
data_insts: self.data_insts,
position: self.position,
Expand All @@ -147,7 +147,7 @@ impl<'a, P: Copy> FuncAtMut<'a, P> {
/// Reposition to `new_position`.
pub fn at<P2: Copy>(self, new_position: P2) -> FuncAtMut<'a, P2> {
FuncAtMut {
control_regions: self.control_regions,
regions: self.regions,
control_nodes: self.control_nodes,
data_insts: self.data_insts,
position: new_position,
Expand All @@ -158,14 +158,14 @@ impl<'a, P: Copy> FuncAtMut<'a, P> {
//
// FIXME(eddyb) maybe find a better name for this?
pub fn freeze(self) -> FuncAt<'a, P> {
let FuncAtMut { control_regions, control_nodes, data_insts, position } = self;
FuncAt { control_regions, control_nodes, data_insts, position }
let FuncAtMut { regions, control_nodes, data_insts, position } = self;
FuncAt { regions, control_nodes, data_insts, position }
}
}

impl<'a> FuncAtMut<'a, ControlRegion> {
pub fn def(self) -> &'a mut ControlRegionDef {
&mut self.control_regions[self.position]
impl<'a> FuncAtMut<'a, Region> {
pub fn def(self) -> &'a mut RegionDef {
&mut self.regions[self.position]
}

pub fn at_children(mut self) -> FuncAtMut<'a, EntityList<ControlNode>> {
Expand Down Expand Up @@ -224,7 +224,7 @@ impl FuncDefBody {
/// Start immutably traversing the function at `position`.
pub fn at<P: Copy>(&self, position: P) -> FuncAt<'_, P> {
FuncAt {
control_regions: &self.control_regions,
regions: &self.regions,
control_nodes: &self.control_nodes,
data_insts: &self.data_insts,
position,
Expand All @@ -234,20 +234,20 @@ impl FuncDefBody {
/// Start mutably traversing the function at `position`.
pub fn at_mut<P: Copy>(&mut self, position: P) -> FuncAtMut<'_, P> {
FuncAtMut {
control_regions: &mut self.control_regions,
regions: &mut self.regions,
control_nodes: &mut self.control_nodes,
data_insts: &mut self.data_insts,
position,
}
}

/// Shorthand for `func_def_body.at(func_def_body.body)`.
pub fn at_body(&self) -> FuncAt<'_, ControlRegion> {
pub fn at_body(&self) -> FuncAt<'_, Region> {
self.at(self.body)
}

/// Shorthand for `func_def_body.at_mut(func_def_body.body)`.
pub fn at_mut_body(&mut self) -> FuncAtMut<'_, ControlRegion> {
pub fn at_mut_body(&mut self) -> FuncAtMut<'_, Region> {
self.at_mut(self.body)
}
}
74 changes: 37 additions & 37 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
// HACK(eddyb) using `(struct.Context.html)` to link `Context`, not `context::Context`.
//! * [`Context`](struct.Context.html): handles interning ([`Type`]s, [`Const`]s, etc.) and allocating entity handles
//! * [`Module`]: owns [`Func`]s and [`GlobalVar`]s (rooted by [`exports`](Module::exports))
//! * [`FuncDefBody`]: owns [`ControlRegion`]s and [DataInst]s (rooted by [`body`](FuncDefBody::body))
//! * [`FuncDefBody`]: owns [`Region`]s and [DataInst]s (rooted by [`body`](FuncDefBody::body))
//!
//! ##### Utilities and passes
//! * [`print`](mod@print): pretty-printer with (styled and hyperlinked) HTML output
Expand Down Expand Up @@ -479,7 +479,7 @@ pub enum TypeKind {
/// attached as `Attr`s on those `Value`s (see [`Attr::QPtr`]).
//
// FIXME(eddyb) a "refinement system" that's orthogonal from types, and kept
// separately in e.g. `ControlRegionInputDecl`, might be a better approach?
// separately in e.g. `RegionInputDecl`, might be a better approach?
QPtr,

SpvInst {
Expand Down Expand Up @@ -624,18 +624,18 @@ pub struct FuncParam {
// FIXME(eddyb) `FuncDefBody`/`func_def_body` are too long, find shorter names.
#[derive(Clone)]
pub struct FuncDefBody {
pub control_regions: EntityDefs<ControlRegion>,
pub regions: EntityDefs<Region>,
pub control_nodes: EntityDefs<ControlNode>,
pub data_insts: EntityDefs<DataInst>,

/// The [`ControlRegion`] representing the whole body of the function.
/// The [`Region`] representing the whole body of the function.
///
/// Function parameters are provided via `body.inputs`, i.e. they can be
/// only accessed with `Value::ControlRegionInputs { region: body, idx }`.
/// only accessed with `Value::RegionInputs { region: body, idx }`.
///
/// When `unstructured_cfg` is `None`, this includes the structured return
/// of the function, with `body.outputs` as the returned values.
pub body: ControlRegion,
pub body: Region,

/// The unstructured (part of the) control-flow graph of the function.
///
Expand All @@ -648,19 +648,19 @@ pub struct FuncDefBody {
pub unstructured_cfg: Option<cfg::ControlFlowGraph>,
}

/// Entity handle for a [`ControlRegionDef`](crate::ControlRegionDef)
/// Entity handle for a [`RegionDef`](crate::RegionDef)
/// (a control-flow region).
///
/// A [`ControlRegion`] ("control-flow region") is a linear chain of [`ControlNode`]s,
/// A [`Region`] ("control-flow region") is a linear chain of [`ControlNode`]s,
/// 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": [`ControlRegion`]s and [`ControlNode`]s 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`]
/// * "structured": [`Region`]s and [`ControlNode`]s in a "mutual tree"
/// * i.e. each such [`Region`] can only appear in exactly one [`ControlNode`],
/// and each [`ControlNode`] can only appear in exactly one [`Region`]
/// * 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
Expand All @@ -670,16 +670,16 @@ pub struct FuncDefBody {
/// [`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": [`ControlRegion`]s which connect to other [`ControlRegion`]s
/// * "unstructured": [`Region`]s which connect to other [`Region`]s
/// using [`cfg::ControlInst`](crate::cfg::ControlInst)s (as described by a
/// [`cfg::ControlFlowGraph`](crate::cfg::ControlFlowGraph))
///
/// When a function's entire body can be described by a single [`ControlRegion`],
/// When a function's entire body can be described by a single [`Region`],
/// 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 [`ControlRegion`]s before
/// by the same CFG edges that were connecting smaller [`Region`]s 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
///
Expand All @@ -705,7 +705,7 @@ pub struct FuncDefBody {
/// (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`](crate::ControlRegion) can be used:
/// * dominance is simpler, so values defined in a [`Region`](crate::Region) 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`](crate::ControlNode)
Expand All @@ -728,37 +728,37 @@ pub struct FuncDefBody {
/// 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 [`ControlRegion`](crate::ControlRegion)s
/// as unstructured control-flow [`Region`](crate::Region)s
/// do *not* themselves get *any* implied dominance relations from the
/// shape of the control-flow graph (unlike most typical CFG+SSA IRs)
pub use context::ControlRegion;
pub use context::Region;

/// Definition for a [`ControlRegion`]: a control-flow region.
/// Definition for a [`Region`]: a control-flow region.
#[derive(Clone, Default)]
pub struct ControlRegionDef {
/// Inputs to this [`ControlRegion`]:
/// * accessed using [`Value::ControlRegionInput`]
pub struct RegionDef {
/// Inputs to this [`Region`]:
/// * accessed using [`Value::RegionInput`]
/// * values provided by the parent:
/// * when this is the function body: the function's parameters
pub inputs: SmallVec<[ControlRegionInputDecl; 2]>,
pub inputs: SmallVec<[RegionInputDecl; 2]>,

pub children: EntityList<ControlNode>,

/// Output values from this [`ControlRegion`], provided to the parent:
/// Output values from this [`Region`], provided to the parent:
/// * when this is the function body: these are the structured return values
/// * when this is a `Select` case: these are the values for the parent
/// [`ControlNode`]'s outputs (accessed using [`Value::ControlNodeOutput`])
/// * when this is a `Loop` body: these are the values to be used for the
/// next loop iteration's body `inputs`
/// * **not** accessible through [`Value::ControlNodeOutput`] on the `Loop`,
/// as it's both confusing regarding [`Value::ControlRegionInput`], and
/// as it's both confusing regarding [`Value::RegionInput`], and
/// also there's nothing stopping body-defined values from directly being
/// used outside the loop (once that changes, this aspect can be flipped)
pub outputs: SmallVec<[Value; 2]>,
}

#[derive(Copy, Clone)]
pub struct ControlRegionInputDecl {
pub struct RegionInputDecl {
pub attrs: AttrSet,

pub ty: Type,
Expand All @@ -767,20 +767,20 @@ pub struct ControlRegionInputDecl {
/// Entity handle for a [`ControlNodeDef`](crate::ControlNodeDef)
/// (a control-flow operator or leaf).
///
/// See [`ControlRegion`] docs for more on control-flow in SPIR-T.
/// See [`Region`] docs for more on control-flow in SPIR-T.
pub use context::ControlNode;

/// Definition for a [`ControlNode`]: a control-flow operator or leaf.
///
/// See [`ControlRegion`] docs for more on control-flow in SPIR-T.
/// See [`Region`] docs for more on control-flow in SPIR-T.
#[derive(Clone)]
pub struct ControlNodeDef {
pub kind: ControlNodeKind,

/// Outputs from this [`ControlNode`]:
/// * accessed using [`Value::ControlNodeOutput`]
/// * values provided by `region.outputs`, where `region` is the executed
/// child [`ControlRegion`]:
/// child [`Region`]:
/// * when this is a `Select`: the case that was chosen
pub outputs: SmallVec<[ControlNodeOutputDecl; 2]>,
}
Expand All @@ -796,20 +796,20 @@ pub struct ControlNodeOutputDecl {
pub enum ControlNodeKind {
/// Linear chain of [`DataInst`]s, executing in sequence.
///
/// This is only an optimization over keeping [`DataInst`]s in [`ControlRegion`]
/// This is only an optimization over keeping [`DataInst`]s in [`Region`]
/// linear chains directly, or even merging [`DataInst`] with [`ControlNode`].
Block {
// FIXME(eddyb) should empty blocks be allowed? should `DataInst`s be
// linked directly into the `ControlRegion` `children` list?
// linked directly into the `Region` `children` list?
insts: EntityList<DataInst>,
},

/// Choose one [`ControlRegion`] out of `cases` to execute, based on a single
/// Choose one [`Region`] out of `cases` to execute, based on a single
/// value input (`scrutinee`) interpreted according to [`SelectionKind`].
///
/// This corresponds to "gamma" (`γ`) nodes in (R)VSDG, though those are
/// sometimes limited only to a two-way selection on a boolean condition.
Select { kind: SelectionKind, scrutinee: Value, cases: SmallVec<[ControlRegion; 2]> },
Select { kind: SelectionKind, scrutinee: Value, cases: SmallVec<[Region; 2]> },

/// Execute `body` repeatedly, until `repeat_condition` evaluates to `false`.
///
Expand All @@ -825,7 +825,7 @@ pub enum ControlNodeKind {
Loop {
initial_inputs: SmallVec<[Value; 2]>,

body: ControlRegion,
body: Region,

// FIXME(eddyb) should this be kept in `body.outputs`? (that would not
// have any ambiguity as to whether it can see `body`-computed values)
Expand Down Expand Up @@ -912,19 +912,19 @@ pub enum DataInstKind {
pub enum Value {
Const(Const),

/// One of the inputs to a [`ControlRegion`]:
/// One of the inputs to a [`Region`]:
/// * declared by `region.inputs[input_idx]`
/// * value provided by the parent of the `region`:
/// * when `region` is the function body: `input_idx`th function parameter
ControlRegionInput {
region: ControlRegion,
RegionInput {
region: Region,
input_idx: u32,
},

/// One of the outputs produced by a [`ControlNode`]:
/// * declared by `control_node.outputs[output_idx]`
/// * value provided by `region.outputs[output_idx]`, where `region` is the
/// executed child [`ControlRegion`] (of `control_node`):
/// executed child [`Region`] (of `control_node`):
/// * when `control_node` is a `Select`: the case that was chosen
ControlNodeOutput {
control_node: ControlNode,
Expand Down
Loading
Loading