Skip to main content

naga/back/msl/
hoist.rs

1/*!
2Finding temporaries that must be declared outside a loop.
3
4Naga IR's [`Loop`] statement runs its `continuing` block after its `body`, but
5MSL has no equivalent construct, so [`Writer::put_block`] emits the `continuing`
6block at the *top* of a `while` loop, skipped on the first iteration:
7
8```ignore
9bool loop_init = true;
10while(true) {
11    if (!loop_init) {
12        <continuing>
13        if (<break_if>) { break; }
14    }
15    loop_init = false;
16    <body>
17}
18```
19
20Expressions emitted in `body` are in scope in `continuing`, but the temporaries
21holding their values are declared *below* the generated `continuing` code, so
22they can't be named there. Re-evaluating such an expression in place would use
23the state at the end of the loop iteration, which may have changed from the
24state at the original position.
25
26So when a loop has a `continuing` block, we declare the temporaries the
27`continuing` block needs ahead of the `while` loop, and have the body merely
28assign to them.
29
30[`Loop`]: crate::Statement::Loop
31[`Writer::put_block`]: super::writer::Writer::put_block
32*/
33
34use core::fmt::Write;
35
36use super::{writer::StatementContext, BackendResult, Writer};
37use crate::{
38    arena::{Arena, Handle, HandleSet},
39    back,
40    compact::ModuleTracer,
41    proc::TypeResolution,
42};
43
44impl<W: Write> Writer<W> {
45    /// Declare the temporaries that a loop's `continuing` block needs ahead of
46    /// the loop.
47    ///
48    /// Record them in [`Self::hoisted_loop_expressions`], so that `continuing` can
49    /// name them and the body assigns to them rather than declaring them. Each
50    /// entry is moved to [`Self::named_expressions`] when the body writes the
51    /// expression's definition, which it always does, since every candidate
52    /// comes from a statement in `body`.
53    ///
54    /// See [the module documentation](self) for why this is needed.
55    pub(super) fn hoist_continuing_dependencies(
56        &mut self,
57        level: back::Level,
58        body: &crate::Block,
59        continuing: &crate::Block,
60        break_if: Option<Handle<crate::Expression>>,
61        context: &StatementContext,
62    ) -> BackendResult {
63        let context = &context.expression;
64        let expressions = &context.function.expressions;
65
66        // The candidates are the definitions that get a temporary at all.
67        //
68        // Skipping the rest is deliberate: `continuing` re-evaluates those in
69        // place, and that is sound, because every expression whose value
70        // depends on *when* it is evaluated already gets a temporary. Loads,
71        // samples and derivatives have a `bake_ref_count` of one, so any use at
72        // all bakes them, and statement results have no inline form. What's
73        // left to re-evaluate is arithmetic and address computation.
74
75        let mut candidates = HandleSet::for_arena(expressions);
76        for handle in definitions(expressions, body).iter() {
77            if self.bakes_expression(handle, context) {
78                assert!(fits_in_temporary(context.module, context.info, handle));
79                candidates.insert(handle);
80            }
81        }
82        if candidates.is_empty() {
83            return Ok(());
84        }
85
86        let used = expressions_used(
87            context.module,
88            context.function,
89            continuing,
90            break_if,
91            &candidates,
92        );
93
94        for handle in candidates.iter() {
95            if !used.contains(handle) {
96                continue;
97            }
98            let Some(name) = self.baked_name(handle, context) else {
99                continue;
100            };
101
102            write!(self.out, "{level}")?;
103            self.put_baked_type(handle, context)?;
104            writeln!(self.out, " {name} = {{}};")?;
105
106            self.hoisted_loop_expressions.insert(handle, name);
107        }
108
109        Ok(())
110    }
111}
112
113/// Return the expressions that `block` itself computes values for, in
114/// increasing handle order.
115fn definitions(
116    expressions: &Arena<crate::Expression>,
117    block: &crate::Block,
118) -> HandleSet<crate::Expression> {
119    use crate::Statement as St;
120
121    let mut defined = HandleSet::for_arena(expressions);
122    for statement in block.iter() {
123        match *statement {
124            St::Emit(ref range) => defined.insert_iter(range.clone()),
125            St::Atomic {
126                result: Some(result),
127                ..
128            }
129            | St::Call {
130                result: Some(result),
131                ..
132            }
133            | St::RayQuery {
134                fun: crate::RayQueryFunction::Proceed { result },
135                ..
136            }
137            | St::WorkGroupUniformLoad { result, .. }
138            | St::SubgroupBallot { result, .. }
139            | St::SubgroupGather { result, .. }
140            | St::SubgroupCollectiveOperation { result, .. } => {
141                defined.insert(result);
142            }
143
144            St::Atomic { result: None, .. }
145            | St::Call { result: None, .. }
146            | St::RayQuery {
147                fun:
148                    crate::RayQueryFunction::Initialize { .. }
149                    | crate::RayQueryFunction::GenerateIntersection { .. }
150                    | crate::RayQueryFunction::ConfirmIntersection
151                    | crate::RayQueryFunction::Terminate
152                    | crate::RayQueryFunction::Begin,
153                ..
154            }
155            | St::Block(_)
156            | St::If { .. }
157            | St::Switch { .. }
158            | St::Loop { .. }
159            | St::Break
160            | St::Continue
161            | St::Return { .. }
162            | St::Kill
163            | St::ControlBarrier(_)
164            | St::MemoryBarrier(_)
165            | St::Store { .. }
166            | St::ImageStore { .. }
167            | St::ImageAtomic { .. }
168            | St::RayPipelineFunction(_)
169            | St::CooperativeStore { .. }
170            | St::DebugPrintf { .. } => {}
171        }
172    }
173    defined
174}
175
176/// Return the expressions of `function` that the code generated for `block`
177/// and `extra_roots` will refer to, directly or indirectly.
178///
179/// This is wider than what `block` needs the values of, because we do no dead
180/// code elimination: [`Writer::put_block`] writes out a definition for every
181/// expression it bakes, whether or not anything reads it, and that definition
182/// refers to the expression's operands. Hence
183/// [`trace_block_including_emits`] rather than [`trace_block`]. Miss those and
184/// a dead definition would leave its operands un-hoisted, and `continuing`
185/// would re-evaluate them in place - or, for a statement result, reach the
186/// `unreachable!` in [`Writer::put_expression`], which has no inline form to
187/// fall back on.
188///
189/// The search does not continue past an expression in `stop_at`: such an
190/// expression is marked if something refers to it, but the search does not
191/// follow it to its operands. Anything reachable only through it is therefore
192/// left out.
193///
194/// [`trace_block_including_emits`]: crate::compact::functions::FunctionTracer::trace_block_including_emits
195/// [`trace_block`]: crate::compact::functions::FunctionTracer::trace_block
196/// [`Writer::put_block`]: super::writer::Writer::put_block
197/// [`Writer::put_expression`]: super::writer::Writer::put_expression
198fn expressions_used(
199    module: &crate::Module,
200    function: &crate::Function,
201    block: &crate::Block,
202    extra_roots: impl IntoIterator<Item = Handle<crate::Expression>>,
203    stop_at: &HandleSet<crate::Expression>,
204) -> HandleSet<crate::Expression> {
205    let mut module_tracer = ModuleTracer::new(module);
206
207    let mut function_tracer = module_tracer.as_function(function);
208    function_tracer.trace_block_including_emits(block);
209    function_tracer.expressions_used.insert_iter(extra_roots);
210
211    let mut expression_tracer = function_tracer.as_expression();
212    for (handle, expression) in function.expressions.iter().rev() {
213        if expression_tracer.expressions_used.contains(handle) && !stop_at.contains(handle) {
214            expression_tracer.trace_expression(expression);
215        }
216    }
217
218    function_tracer.expressions_used
219}
220
221/// Whether `expression` is the result of a statement.
222///
223/// There is no way to write these inline, so the statement that produces one
224/// always stores it in a temporary, whether or not anything asked for one.
225pub(super) const fn is_statement_result(expression: &crate::Expression) -> bool {
226    use crate::Expression as Ex;
227    match *expression {
228        Ex::CallResult(_)
229        | Ex::AtomicResult { .. }
230        | Ex::WorkGroupUniformLoadResult { .. }
231        | Ex::SubgroupBallotResult
232        | Ex::SubgroupOperationResult { .. }
233        | Ex::RayQueryProceedResult => true,
234
235        Ex::Literal(_)
236        | Ex::Constant(_)
237        | Ex::Override(_)
238        | Ex::ZeroValue(_)
239        | Ex::Compose { .. }
240        | Ex::Access { .. }
241        | Ex::AccessIndex { .. }
242        | Ex::Splat { .. }
243        | Ex::Swizzle { .. }
244        | Ex::FunctionArgument(_)
245        | Ex::GlobalVariable(_)
246        | Ex::LocalVariable(_)
247        | Ex::Load { .. }
248        | Ex::ImageSample { .. }
249        | Ex::ImageLoad { .. }
250        | Ex::ImageQuery { .. }
251        | Ex::Unary { .. }
252        | Ex::Binary { .. }
253        | Ex::Select { .. }
254        | Ex::Derivative { .. }
255        | Ex::Relational { .. }
256        | Ex::Math { .. }
257        | Ex::As { .. }
258        | Ex::Bitcast { .. }
259        | Ex::ArrayLength(_)
260        | Ex::RayQueryVertexPositions { .. }
261        | Ex::RayQueryGetIntersection { .. }
262        | Ex::CooperativeLoad { .. }
263        | Ex::CooperativeMultiplyAdd { .. } => false,
264    }
265}
266
267/// Whether a value of `handle`'s type can be held in a default-initialized
268/// local temporary.
269fn fits_in_temporary(
270    module: &crate::Module,
271    info: &crate::valid::FunctionInfo,
272    handle: Handle<crate::Expression>,
273) -> bool {
274    use crate::TypeInner as Ti;
275    match info[handle].ty {
276        TypeResolution::Handle(ty) => matches!(
277            module.types[ty].inner,
278            Ti::Scalar(_)
279                | Ti::Vector { .. }
280                | Ti::Matrix { .. }
281                | Ti::Array {
282                    size: crate::ArraySize::Constant(_),
283                    ..
284                }
285                | Ti::Struct { .. }
286                | Ti::CooperativeMatrix { .. }
287        ),
288        TypeResolution::Value(
289            Ti::Scalar(_) | Ti::Vector { .. } | Ti::Matrix { .. } | Ti::CooperativeMatrix { .. },
290        ) => true,
291        TypeResolution::Value(_) => false,
292    }
293}