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}