naga/back/spv/
selection.rs

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