summaryrefslogtreecommitdiffstats
path: root/third_party/rust/naga/src/back/spv/selection.rs
blob: 788b1f10ab81faea0fede14c963d668ad17c4098 (plain)
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)]) {}
}