1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
/*!
Generate SPIR-V conditional structures.
Builders for `if` structures with `and`s.
The types in this module track the information needed to emit SPIR-V code
for complex conditional structures, like those whose conditions involve
short-circuiting 'and' and 'or' structures. These track labels and can emit
`OpPhi` instructions to merge values produced along different paths.
This currently only supports exactly the forms Naga uses, so it doesn't
support `or` or `else`, and only supports zero or one merged values.
Naga needs to emit code roughly like this:
```ignore
value = DEFAULT;
if COND1 && COND2 {
value = THEN_VALUE;
}
// use value
```
Assuming `ctx` and `block` are a mutable references to a [`BlockContext`]
and the current [`Block`], and `merge_type` is the SPIR-V type for the
merged value `value`, we can build SPIR-V for the code above like so:
```ignore
let cond = Selection::start(block, merge_type);
// ... compute `cond1` ...
cond.if_true(ctx, cond1, DEFAULT);
// ... compute `cond2` ...
cond.if_true(ctx, cond2, DEFAULT);
// ... compute THEN_VALUE
let merged_value = cond.finish(ctx, THEN_VALUE);
```
After this, `merged_value` is either `DEFAULT` or `THEN_VALUE`, depending on
the path by which the merged block was reached.
This takes care of writing all branch instructions, including an
`OpSelectionMerge` annotation in the header block; starting new blocks and
assigning them labels; and emitting the `OpPhi` that gathers together the
right sources for the merged values, for every path through the selection
construct.
When there is no merged value to produce, you can pass `()` for `merge_type`
and the merge values. In this case no `OpPhi` instructions are produced, and
the `finish` method returns `()`.
To enforce proper nesting, a `Selection` takes ownership of the `&mut Block`
pointer for the duration of its lifetime. To obtain the block for generating
code in the selection's body, call the `Selection::block` method.
*/
use super::{Block, BlockContext, Instruction};
use spirv::Word;
/// A private struct recording what we know about the selection construct so far.
pub(super) struct Selection<'b, M: MergeTuple> {
/// The block pointer we're emitting code into.
block: &'b mut Block,
/// The label of the selection construct's merge block, or `None` if we
/// haven't yet written the `OpSelectionMerge` merge instruction.
merge_label: Option<Word>,
/// A set of `(VALUES, PARENT)` pairs, used to build `OpPhi` instructions in
/// the merge block. Each `PARENT` is the label of a predecessor block of
/// the merge block. The corresponding `VALUES` holds the ids of the values
/// that `PARENT` contributes to the merged values.
///
/// We emit all branches to the merge block, so we know all its
/// predecessors. And we refuse to emit a branch unless we're given the
/// values the branching block contributes to the merge, so we always have
/// everything we need to emit the correct phis, by construction.
values: Vec<(M, Word)>,
/// The types of the values in each element of `values`.
merge_types: M,
}
impl<'b, M: MergeTuple> Selection<'b, M> {
/// Start a new selection construct.
///
/// The `block` argument indicates the selection's header block.
///
/// The `merge_types` argument should be a `Word` or tuple of `Word`s, each
/// value being the SPIR-V result type id of an `OpPhi` instruction that
/// will be written to the selection's merge block when this selection's
/// [`finish`] method is called. This argument may also be `()`, for
/// selections that produce no values.
///
/// (This function writes no code to `block` itself; it simply constructs a
/// fresh `Selection`.)
///
/// [`finish`]: Selection::finish
pub(super) fn start(block: &'b mut Block, merge_types: M) -> Self {
Selection {
block,
merge_label: None,
values: vec![],
merge_types,
}
}
pub(super) fn block(&mut self) -> &mut Block {
self.block
}
/// Branch to a successor block if `cond` is true, otherwise merge.
///
/// If `cond` is false, branch to the merge block, using `values` as the
/// merged values. Otherwise, proceed to a new block.
///
/// The `values` argument must be the same shape as the `merge_types`
/// argument passed to `Selection::start`.
pub(super) fn if_true(&mut self, ctx: &mut BlockContext, cond: Word, values: M) {
self.values.push((values, self.block.label_id));
let merge_label = self.make_merge_label(ctx);
let next_label = ctx.gen_id();
ctx.function.consume(
std::mem::replace(self.block, Block::new(next_label)),
Instruction::branch_conditional(cond, next_label, merge_label),
);
}
/// Emit an unconditional branch to the merge block, and compute merged
/// values.
///
/// Use `final_values` as the merged values contributed by the current
/// block, and transition to the merge block, emitting `OpPhi` instructions
/// to produce the merged values. This must be the same shape as the
/// `merge_types` argument passed to [`Selection::start`].
///
/// Return the SPIR-V ids of the merged values. This value has the same
/// shape as the `merge_types` argument passed to `Selection::start`.
pub(super) fn finish(self, ctx: &mut BlockContext, final_values: M) -> M {
match self {
Selection {
merge_label: None, ..
} => {
// We didn't actually emit any branches, so `self.values` must
// be empty, and `final_values` are the only sources we have for
// the merged values. Easy peasy.
final_values
}
Selection {
block,
merge_label: Some(merge_label),
mut values,
merge_types,
} => {
// Emit the final branch and transition to the merge block.
values.push((final_values, block.label_id));
ctx.function.consume(
std::mem::replace(block, Block::new(merge_label)),
Instruction::branch(merge_label),
);
// Now that we're in the merge block, build the phi instructions.
merge_types.write_phis(ctx, block, &values)
}
}
}
/// Return the id of the merge block, writing a merge instruction if needed.
fn make_merge_label(&mut self, ctx: &mut BlockContext) -> Word {
match self.merge_label {
None => {
let merge_label = ctx.gen_id();
self.block.body.push(Instruction::selection_merge(
merge_label,
spirv::SelectionControl::NONE,
));
self.merge_label = Some(merge_label);
merge_label
}
Some(merge_label) => merge_label,
}
}
}
/// A trait to help `Selection` manage any number of merged values.
///
/// Some selection constructs, like a `ReadZeroSkipWrite` bounds check on a
/// [`Load`] expression, produce a single merged value. Others produce no merged
/// value, like a bounds check on a [`Store`] statement.
///
/// To let `Selection` work nicely with both cases, we let the merge type
/// argument passed to [`Selection::start`] be any type that implements this
/// `MergeTuple` trait. `MergeTuple` is then implemented for `()`, `Word`,
/// `(Word, Word)`, and so on.
///
/// A `MergeTuple` type can represent either a bunch of SPIR-V types or values;
/// the `merge_types` argument to `Selection::start` are type ids, whereas the
/// `values` arguments to the [`if_true`] and [`finish`] methods are value ids.
/// The set of merged value returned by `finish` is a tuple of value ids.
///
/// In fact, since Naga only uses zero- and single-valued selection constructs
/// at present, we only implement `MergeTuple` for `()` and `Word`. But if you
/// add more cases, feel free to add more implementations. Once const generics
/// are available, we could have a single implementation of `MergeTuple` for all
/// lengths of arrays, and be done with it.
///
/// [`Load`]: crate::Expression::Load
/// [`Store`]: crate::Statement::Store
/// [`if_true`]: Selection::if_true
/// [`finish`]: Selection::finish
pub(super) trait MergeTuple: Sized {
/// Write OpPhi instructions for the given set of predecessors.
///
/// The `predecessors` vector should be a vector of `(LABEL, VALUES)` pairs,
/// where each `VALUES` holds the values contributed by the branch from
/// `LABEL`, which should be one of the current block's predecessors.
fn write_phis(
self,
ctx: &mut BlockContext,
block: &mut Block,
predecessors: &[(Self, Word)],
) -> Self;
}
/// Selections that produce a single merged value.
///
/// For example, `ImageLoad` with `BoundsCheckPolicy::ReadZeroSkipWrite` either
/// returns a texel value or zeros.
impl MergeTuple for Word {
fn write_phis(
self,
ctx: &mut BlockContext,
block: &mut Block,
predecessors: &[(Word, Word)],
) -> Word {
let merged_value = ctx.gen_id();
block
.body
.push(Instruction::phi(self, merged_value, predecessors));
merged_value
}
}
/// Selections that produce no merged values.
///
/// For example, `ImageStore` under `BoundsCheckPolicy::ReadZeroSkipWrite`
/// either does the store or skips it, but in neither case does it produce a
/// value.
impl MergeTuple for () {
/// No phis need to be generated.
fn write_phis(self, _: &mut BlockContext, _: &mut Block, _: &[((), Word)]) {}
}