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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
//! Workarounds for platform bugs and limitations in switches and loops.
//!
//! In these docs, we use CamelCase links for Naga IR concepts, and ordinary
//! `code` formatting for HLSL or GLSL concepts.
//!
//! ## Avoiding `continue` within `switch`
//!
//! As described in <https://github.com/gfx-rs/wgpu/issues/4485>, the FXC HLSL
//! compiler doesn't allow `continue` statements within `switch` statements, but
//! Naga IR does. We work around this by introducing synthetic boolean local
//! variables and branches.
//!
//! Specifically:
//!
//! - We generate code for [`Continue`] statements within [`SwitchCase`]s that
//!   sets an introduced `bool` local to `true` and does a `break`, jumping to
//!   immediately after the generated `switch`.
//!
//! - When generating code for a [`Switch`] statement, we conservatively assume
//!   it might contain such a [`Continue`] statement, so:
//!
//!   - If it's the outermost such [`Switch`] within a [`Loop`], we declare the
//!     `bool` local ahead of the switch, initialized to `false`. Immediately
//!     after the `switch`, we check the local and do a `continue` if it's set.
//!
//!   - If the [`Switch`] is nested within other [`Switch`]es, then after the
//!     generated `switch`, we check the local (which we know was declared
//!     before the surrounding `switch`) and do a `break` if it's set.
//!
//!   - As an optimization, we only generate the check of the local if a
//!     [`Continue`] statement is encountered within the [`Switch`]. This may
//!     help drivers more easily identify that the `bool` is unused.
//!
//! So while we "weaken" the [`Continue`] statement by rendering it as a `break`
//! statement, we also place checks immediately at the locations to which those
//! `break` statements will jump, until we can be sure we've reached the
//! intended target of the original [`Continue`].
//!
//! In the case of nested [`Loop`] and [`Switch`] statements, there may be
//! multiple introduced `bool` locals in scope, but there's no problem knowing
//! which one to operate on. At any point, there is at most one [`Loop`]
//! statement that could be targeted by a [`Continue`] statement, so the correct
//! `bool` local to set and test is always the one introduced for the innermost
//! enclosing [`Loop`]'s outermost [`Switch`].
//!
//! # Avoiding single body `switch` statements
//!
//! As described in <https://github.com/gfx-rs/wgpu/issues/4514>, some language
//! front ends miscompile `switch` statements where all cases branch to the same
//! body. Our HLSL and GLSL backends render [`Switch`] statements with a single
//! [`SwitchCase`] as `do {} while(false);` loops.
//!
//! However, this rewriting introduces a new loop that could "capture"
//! `continue` statements in its body. To avoid doing so, we apply the
//! [`Continue`]-to-`break` transformation described above.
//!
//! [`Continue`]: crate::Statement::Continue
//! [`Loop`]: crate::Statement::Loop
//! [`Switch`]: crate::Statement::Switch
//! [`SwitchCase`]: crate::SwitchCase

use crate::proc::Namer;
use std::rc::Rc;

/// A summary of the code surrounding a statement.
enum Nesting {
    /// Currently nested in at least one [`Loop`] statement.
    ///
    /// [`Continue`] should apply to the innermost loop.
    ///
    /// When this entry is on the top of the stack:
    ///
    /// * When entering an inner [`Loop`] statement, push a [`Loop`][nl] state
    ///   onto the stack.
    ///
    /// * When entering a nested [`Switch`] statement, push a [`Switch`][ns]
    ///   state onto the stack with a new variable name. Before the generated
    ///   `switch`, introduce a `bool` local with that name, initialized to
    ///   `false`.
    ///
    /// When exiting the [`Loop`] for which this entry was pushed, pop it from
    /// the stack.
    ///
    /// [`Continue`]: crate::Statement::Continue
    /// [`Loop`]: crate::Statement::Loop
    /// [`Switch`]: crate::Statement::Switch
    /// [ns]: Nesting::Switch
    /// [nl]: Nesting::Loop
    Loop,

    /// Currently nested in at least one [`Switch`] that may need to forward
    /// [`Continue`]s.
    ///
    /// This includes [`Switch`]es rendered as `do {} while(false)` loops, but
    /// doesn't need to include regular [`Switch`]es in backends that can
    /// support `continue` within switches.
    ///
    /// [`Continue`] should be forwarded to the innermost surrounding [`Loop`].
    ///
    /// When this entry is on the top of the stack:
    ///
    /// * When entering a nested [`Loop`], push a [`Loop`][nl] state onto the
    ///   stack.
    ///
    /// * When entering a nested [`Switch`], push a [`Switch`][ns] state onto
    ///   the stack with a clone of the introduced `bool` variable's name.
    ///
    /// * When encountering a [`Continue`] statement, render it as code to set
    ///   the introduced `bool` local (whose name is held in [`variable`]) to
    ///   `true`, and then `break`. Set [`continue_encountered`] to `true` to
    ///   record that the [`Switch`] contains a [`Continue`].
    ///
    /// * When exiting this [`Switch`], pop its entry from the stack. If
    ///   [`continue_encountered`] is set, then we have rendered [`Continue`]
    ///   statements as `break` statements that jump to this point. Generate
    ///   code to check `variable`, and if it is `true`:
    ///
    ///     * If there is another [`Switch`][ns] left on top of the stack, set
    ///       its `continue_encountered` flag, and generate a `break`. (Both
    ///       [`Switch`][ns]es are within the same [`Loop`] and share the same
    ///       introduced variable, so there's no need to set another flag to
    ///       continue to exit the `switch`es.)
    ///
    ///     * Otherwise, `continue`.
    ///
    /// When we exit the [`Switch`] for which this entry was pushed, pop it.
    ///
    /// [`Continue`]: crate::Statement::Continue
    /// [`Loop`]: crate::Statement::Loop
    /// [`Switch`]: crate::Statement::Switch
    /// [`variable`]: Nesting::Switch::variable
    /// [`continue_encountered`]: Nesting::Switch::continue_encountered
    /// [ns]: Nesting::Switch
    /// [nl]: Nesting::Loop
    Switch {
        variable: Rc<String>,

        /// Set if we've generated code for a [`Continue`] statement with this
        /// entry on the top of the stack.
        ///
        /// If this is still clear when we finish rendering the [`Switch`], then
        /// we know we don't need to generate branch forwarding code. Omitting
        /// that may make it easier for drivers to tell that the `bool` we
        /// introduced ahead of the [`Switch`] is actually unused.
        ///
        /// [`Continue`]: crate::Statement::Continue
        /// [`Switch`]: crate::Statement::Switch
        continue_encountered: bool,
    },
}

/// A micro-IR for code a backend should generate after a [`Switch`].
///
/// [`Switch`]: crate::Statement::Switch
pub(super) enum ExitControlFlow {
    None,
    /// Emit `if (continue_variable) { continue; }`
    Continue {
        variable: Rc<String>,
    },
    /// Emit `if (continue_variable) { break; }`
    ///
    /// Used after a [`Switch`] to exit from an enclosing [`Switch`].
    ///
    /// After the enclosing switch, its associated check will consult this same
    /// variable, see that it is set, and exit early.
    ///
    /// [`Switch`]: crate::Statement::Switch
    Break {
        variable: Rc<String>,
    },
}

/// Utility for tracking nesting of loops and switches to orchestrate forwarding
/// of continue statements inside of a switch to the enclosing loop.
///
/// See [module docs](self) for why we need this.
#[derive(Default)]
pub(super) struct ContinueCtx {
    stack: Vec<Nesting>,
}

impl ContinueCtx {
    /// Resets internal state.
    ///
    /// Use this to reuse memory between writing sessions.
    pub fn clear(&mut self) {
        self.stack.clear();
    }

    /// Updates internal state to record entering a [`Loop`] statement.
    ///
    /// [`Loop`]: crate::Statement::Loop
    pub fn enter_loop(&mut self) {
        self.stack.push(Nesting::Loop);
    }

    /// Updates internal state to record exiting a [`Loop`] statement.
    ///
    /// [`Loop`]: crate::Statement::Loop
    pub fn exit_loop(&mut self) {
        if !matches!(self.stack.pop(), Some(Nesting::Loop)) {
            unreachable!("ContinueCtx stack out of sync");
        }
    }

    /// Updates internal state to record entering a [`Switch`] statement.
    ///
    /// Return `Some(variable)` if this [`Switch`] is nested within a [`Loop`],
    /// and the caller should introcue a new `bool` local variable named
    /// `variable` above the `switch`, for forwarding [`Continue`] statements.
    ///
    /// `variable` is guaranteed not to conflict with any names used by the
    /// program itself.
    ///
    /// [`Continue`]: crate::Statement::Continue
    /// [`Loop`]: crate::Statement::Loop
    /// [`Switch`]: crate::Statement::Switch
    pub fn enter_switch(&mut self, namer: &mut Namer) -> Option<Rc<String>> {
        match self.stack.last() {
            // If the stack is empty, we are not in loop, so we don't need to
            // forward continue statements within this `Switch`. We can leave
            // the stack empty.
            None => None,
            Some(&Nesting::Loop { .. }) => {
                let variable = Rc::new(namer.call("should_continue"));
                self.stack.push(Nesting::Switch {
                    variable: Rc::clone(&variable),
                    continue_encountered: false,
                });
                Some(variable)
            }
            Some(&Nesting::Switch { ref variable, .. }) => {
                self.stack.push(Nesting::Switch {
                    variable: Rc::clone(variable),
                    continue_encountered: false,
                });
                // We have already declared the variable before some enclosing
                // `Switch`.
                None
            }
        }
    }

    /// Update internal state to record leaving a [`Switch`] statement.
    ///
    /// Return an [`ExitControlFlow`] value indicating what code should be
    /// introduced after the generated `switch` to forward continues.
    ///
    /// [`Switch`]: crate::Statement::Switch
    pub fn exit_switch(&mut self) -> ExitControlFlow {
        match self.stack.pop() {
            // This doesn't indicate a problem: we don't start pushing entries
            // for `Switch` statements unless we have an enclosing `Loop`.
            None => ExitControlFlow::None,
            Some(Nesting::Loop { .. }) => {
                unreachable!("Unexpected loop state when exiting switch");
            }
            Some(Nesting::Switch {
                variable,
                continue_encountered: inner_continue,
            }) => {
                if !inner_continue {
                    // No `Continue` statement was encountered, so we didn't
                    // introduce any `break`s jumping to this point.
                    ExitControlFlow::None
                } else if let Some(&mut Nesting::Switch {
                    continue_encountered: ref mut outer_continue,
                    ..
                }) = self.stack.last_mut()
                {
                    // This is nested in another `Switch`. Propagate upwards
                    // that there is a continue statement present.
                    *outer_continue = true;
                    ExitControlFlow::Break { variable }
                } else {
                    ExitControlFlow::Continue { variable }
                }
            }
        }
    }

    /// Determine what to generate for a [`Continue`] statement.
    ///
    /// If we can generate an ordinary `continue` statement, return `None`.
    ///
    /// Otherwise, we're enclosed by a [`Switch`] that is itself enclosed by a
    /// [`Loop`]. Return `Some(variable)` to indicate that the [`Continue`]
    /// should be rendered as setting `variable` to `true`, and then doing a
    /// `break`.
    ///
    /// This also notes that we've encountered a [`Continue`] statement, so that
    /// we can generate the right code to forward the branch following the
    /// enclosing `switch`.
    ///
    /// [`Continue`]: crate::Statement::Continue
    /// [`Loop`]: crate::Statement::Loop
    /// [`Switch`]: crate::Statement::Switch
    pub fn continue_encountered(&mut self) -> Option<&str> {
        if let Some(&mut Nesting::Switch {
            ref variable,
            ref mut continue_encountered,
        }) = self.stack.last_mut()
        {
            *continue_encountered = true;
            Some(variable)
        } else {
            None
        }
    }
}