/*! Finding temporaries that must be declared outside a loop. Naga IR's [`Loop`] statement runs its `continuing` block after its `body`, but MSL has no equivalent construct, so [`Writer::put_block`] emits the `continuing` block at the *top* of a `while` loop, skipped on the first iteration: ```ignore bool loop_init = true; while(true) { if (!loop_init) { if () { break; } } loop_init = false; } ``` Expressions emitted in `body` are in scope in `continuing`, but the temporaries holding their values are declared *below* the generated `continuing` code, so they can't be named there. Re-evaluating such an expression in place would use the state at the end of the loop iteration, which may have changed from the state at the original position. So when a loop has a `continuing` block, we declare the temporaries the `continuing` block needs ahead of the `while` loop, and have the body merely assign to them. [`Loop`]: crate::Statement::Loop [`Writer::put_block`]: super::writer::Writer::put_block */ use core::fmt::Write; use super::{writer::StatementContext, BackendResult, Writer}; use crate::{ arena::{Arena, Handle, HandleSet}, back, compact::ModuleTracer, proc::TypeResolution, }; impl Writer { /// Declare the temporaries that a loop's `continuing` block needs ahead of /// the loop. /// /// Record them in [`Self::hoisted_loop_expressions`], so that `continuing` can /// name them and the body assigns to them rather than declaring them. Each /// entry is moved to [`Self::named_expressions`] when the body writes the /// expression's definition, which it always does, since every candidate /// comes from a statement in `body`. /// /// See [the module documentation](self) for why this is needed. pub(super) fn hoist_continuing_dependencies( &mut self, level: back::Level, body: &crate::Block, continuing: &crate::Block, break_if: Option>, context: &StatementContext, ) -> BackendResult { let context = &context.expression; let expressions = &context.function.expressions; // The candidates are the definitions that get a temporary at all. // // Skipping the rest is deliberate: `continuing` re-evaluates those in // place, and that is sound, because every expression whose value // depends on *when* it is evaluated already gets a temporary. Loads, // samples and derivatives have a `bake_ref_count` of one, so any use at // all bakes them, and statement results have no inline form. What's // left to re-evaluate is arithmetic and address computation. let mut candidates = HandleSet::for_arena(expressions); for handle in definitions(expressions, body).iter() { if self.bakes_expression(handle, context) { assert!(fits_in_temporary(context.module, context.info, handle)); candidates.insert(handle); } } if candidates.is_empty() { return Ok(()); } let used = expressions_used( context.module, context.function, continuing, break_if, &candidates, ); for handle in candidates.iter() { if !used.contains(handle) { continue; } let Some(name) = self.baked_name(handle, context) else { continue; }; write!(self.out, "{level}")?; self.put_baked_type(handle, context)?; writeln!(self.out, " {name} = {{}};")?; self.hoisted_loop_expressions.insert(handle, name); } Ok(()) } } /// Return the expressions that `block` itself computes values for, in /// increasing handle order. fn definitions( expressions: &Arena, block: &crate::Block, ) -> HandleSet { use crate::Statement as St; let mut defined = HandleSet::for_arena(expressions); for statement in block.iter() { match *statement { St::Emit(ref range) => defined.insert_iter(range.clone()), St::Atomic { result: Some(result), .. } | St::Call { result: Some(result), .. } | St::RayQuery { fun: crate::RayQueryFunction::Proceed { result }, .. } | St::WorkGroupUniformLoad { result, .. } | St::SubgroupBallot { result, .. } | St::SubgroupGather { result, .. } | St::SubgroupCollectiveOperation { result, .. } => { defined.insert(result); } St::Atomic { result: None, .. } | St::Call { result: None, .. } | St::RayQuery { fun: crate::RayQueryFunction::Initialize { .. } | crate::RayQueryFunction::GenerateIntersection { .. } | crate::RayQueryFunction::ConfirmIntersection | crate::RayQueryFunction::Terminate | crate::RayQueryFunction::Begin, .. } | St::Block(_) | St::If { .. } | St::Switch { .. } | St::Loop { .. } | St::Break | St::Continue | St::Return { .. } | St::Kill | St::ControlBarrier(_) | St::MemoryBarrier(_) | St::Store { .. } | St::ImageStore { .. } | St::ImageAtomic { .. } | St::RayPipelineFunction(_) | St::CooperativeStore { .. } | St::DebugPrintf { .. } => {} } } defined } /// Return the expressions of `function` that the code generated for `block` /// and `extra_roots` will refer to, directly or indirectly. /// /// This is wider than what `block` needs the values of, because we do no dead /// code elimination: [`Writer::put_block`] writes out a definition for every /// expression it bakes, whether or not anything reads it, and that definition /// refers to the expression's operands. Hence /// [`trace_block_including_emits`] rather than [`trace_block`]. Miss those and /// a dead definition would leave its operands un-hoisted, and `continuing` /// would re-evaluate them in place - or, for a statement result, reach the /// `unreachable!` in [`Writer::put_expression`], which has no inline form to /// fall back on. /// /// The search does not continue past an expression in `stop_at`: such an /// expression is marked if something refers to it, but the search does not /// follow it to its operands. Anything reachable only through it is therefore /// left out. /// /// [`trace_block_including_emits`]: crate::compact::functions::FunctionTracer::trace_block_including_emits /// [`trace_block`]: crate::compact::functions::FunctionTracer::trace_block /// [`Writer::put_block`]: super::writer::Writer::put_block /// [`Writer::put_expression`]: super::writer::Writer::put_expression fn expressions_used( module: &crate::Module, function: &crate::Function, block: &crate::Block, extra_roots: impl IntoIterator>, stop_at: &HandleSet, ) -> HandleSet { let mut module_tracer = ModuleTracer::new(module); let mut function_tracer = module_tracer.as_function(function); function_tracer.trace_block_including_emits(block); function_tracer.expressions_used.insert_iter(extra_roots); let mut expression_tracer = function_tracer.as_expression(); for (handle, expression) in function.expressions.iter().rev() { if expression_tracer.expressions_used.contains(handle) && !stop_at.contains(handle) { expression_tracer.trace_expression(expression); } } function_tracer.expressions_used } /// Whether `expression` is the result of a statement. /// /// There is no way to write these inline, so the statement that produces one /// always stores it in a temporary, whether or not anything asked for one. pub(super) const fn is_statement_result(expression: &crate::Expression) -> bool { use crate::Expression as Ex; match *expression { Ex::CallResult(_) | Ex::AtomicResult { .. } | Ex::WorkGroupUniformLoadResult { .. } | Ex::SubgroupBallotResult | Ex::SubgroupOperationResult { .. } | Ex::RayQueryProceedResult => true, Ex::Literal(_) | Ex::Constant(_) | Ex::Override(_) | Ex::ZeroValue(_) | Ex::Compose { .. } | Ex::Access { .. } | Ex::AccessIndex { .. } | Ex::Splat { .. } | Ex::Swizzle { .. } | Ex::FunctionArgument(_) | Ex::GlobalVariable(_) | Ex::LocalVariable(_) | Ex::Load { .. } | Ex::ImageSample { .. } | Ex::ImageLoad { .. } | Ex::ImageQuery { .. } | Ex::Unary { .. } | Ex::Binary { .. } | Ex::Select { .. } | Ex::Derivative { .. } | Ex::Relational { .. } | Ex::Math { .. } | Ex::As { .. } | Ex::ArrayLength(_) | Ex::RayQueryVertexPositions { .. } | Ex::RayQueryGetIntersection { .. } | Ex::CooperativeLoad { .. } | Ex::CooperativeMultiplyAdd { .. } => false, } } /// Whether a value of `handle`'s type can be held in a default-initialized /// local temporary. fn fits_in_temporary( module: &crate::Module, info: &crate::valid::FunctionInfo, handle: Handle, ) -> bool { use crate::TypeInner as Ti; match info[handle].ty { TypeResolution::Handle(ty) => matches!( module.types[ty].inner, Ti::Scalar(_) | Ti::Vector { .. } | Ti::Matrix { .. } | Ti::Array { size: crate::ArraySize::Constant(_), .. } | Ti::Struct { .. } | Ti::CooperativeMatrix { .. } ), TypeResolution::Value( Ti::Scalar(_) | Ti::Vector { .. } | Ti::Matrix { .. } | Ti::CooperativeMatrix { .. }, ) => true, TypeResolution::Value(_) => false, } }