11//! Copy and constant propagation transformation pass.
22//!
3- //! This pass propagates constant values through the MIR by tracking which locals hold known
4- //! constants and substituting uses of those locals with the constant values directly .
3+ //! This pass propagates both constants and copies through the MIR by tracking which locals hold
4+ //! known values (either constants or references to other locals) and substituting uses accordingly .
55//!
66//! Unlike [`ForwardSubstitution`], this pass does not perform full data dependency analysis and
77//! cannot resolve values through projections or chained access paths. It is faster but less
8- //! comprehensive, making it suitable for quick constant folding in simpler cases.
8+ //! comprehensive, making it suitable for quick propagation in simpler cases.
99//!
1010//! # Algorithm
1111//!
1212//! The pass operates in a single forward traversal (reverse postorder):
1313//!
14- //! 1. For each block, propagates constants through block parameters when all predecessors pass the
15- //! same constant value
16- //! 2. For each assignment `_x = <operand>`, if the operand is a constant or a local known to hold a
17- //! constant, records that `_x` holds that constant
18- //! 3. For each use of a local known to hold a constant, substitutes the use with the constant
14+ //! 1. For each block, propagates values through block parameters when all predecessors pass the
15+ //! same value (constant or local)
16+ //! 2. For each assignment `_x = <operand>`, records what `_x` holds:
17+ //! - If the operand is a constant, records `_x → constant`
18+ //! - If the operand is a local (possibly with a known value), records `_x → known value`
19+ //! 3. For each use of a local with a known value, substitutes the use with that value
20+ //!
21+ //! # Examples
22+ //!
23+ //! Constant propagation:
24+ //! ```text
25+ //! _1 = const 42; use(_1) → _1 = const 42; use(const 42)
26+ //! ```
27+ //!
28+ //! Copy propagation:
29+ //! ```text
30+ //! _2 = _1; use(_2) → _2 = _1; use(_1)
31+ //! ```
32+ //!
33+ //! Chained propagation:
34+ //! ```text
35+ //! _2 = _1; _3 = _2; use(_3) → _2 = _1; _3 = _1; use(_1)
36+ //! ```
1937//!
2038//! # Limitations
2139//!
2240//! - Does not handle projections: `_2 = (_1,); use(_2.0)` is not simplified
2341//! - Does not perform fix-point iteration for loops
24- //! - Only tracks constants, not arbitrary value equivalences
42+ //! - Assumes SSA-like semantics (locals are assigned at most once)
2543//!
2644//! For more comprehensive value propagation including projections, see [`ForwardSubstitution`].
2745//!
@@ -46,6 +64,7 @@ use crate::{
4664 local:: { Local , LocalVec } ,
4765 location:: Location ,
4866 operand:: Operand ,
67+ place:: Place ,
4968 rvalue:: RValue ,
5069 statement:: Assign ,
5170 } ,
@@ -165,6 +184,21 @@ where
165184 . flatten ( )
166185}
167186
187+ #[ derive( Debug , Copy , Clone , PartialEq , Eq , Hash ) ]
188+ enum KnownValue < ' heap > {
189+ Constant ( Constant < ' heap > ) ,
190+ Local ( Local ) ,
191+ }
192+
193+ impl < ' heap > From < KnownValue < ' heap > > for Operand < ' heap > {
194+ fn from ( value : KnownValue < ' heap > ) -> Self {
195+ match value {
196+ KnownValue :: Constant ( constant) => Operand :: Constant ( constant) ,
197+ KnownValue :: Local ( local) => Operand :: Place ( Place :: local ( local) ) ,
198+ }
199+ }
200+ }
201+
168202pub struct CopyPropagation < A : BumpAllocator = Scratch > {
169203 alloc : A ,
170204}
@@ -196,7 +230,7 @@ impl<'env, 'heap, A: ResetAllocator> TransformPass<'env, 'heap> for CopyPropagat
196230
197231 let mut visitor = CopyPropagationVisitor {
198232 interner : context. interner ,
199- constants : IdVec :: with_capacity_in ( body. local_decls . len ( ) , & self . alloc ) ,
233+ values : IdVec :: with_capacity_in ( body. local_decls . len ( ) , & self . alloc ) ,
200234 changed : false ,
201235 } ;
202236
@@ -208,10 +242,10 @@ impl<'env, 'heap, A: ResetAllocator> TransformPass<'env, 'heap> for CopyPropagat
208242 let mut args = Vec :: new_in ( & self . alloc ) ;
209243
210244 for & mut id in reverse_postorder {
211- for ( local, constant ) in
245+ for ( local, value ) in
212246 propagate_block_params ( & mut args, body, id, |operand| visitor. try_eval ( operand) )
213247 {
214- visitor. constants . insert ( local, constant ) ;
248+ visitor. values . insert ( local, value ) ;
215249 }
216250
217251 Ok ( ( ) ) =
@@ -224,7 +258,7 @@ impl<'env, 'heap, A: ResetAllocator> TransformPass<'env, 'heap> for CopyPropagat
224258
225259struct CopyPropagationVisitor < ' env , ' heap , A : Allocator > {
226260 interner : & ' env Interner < ' heap > ,
227- constants : LocalVec < Option < Constant < ' heap > > , A > ,
261+ values : LocalVec < Option < KnownValue < ' heap > > , A > ,
228262 changed : bool ,
229263}
230264
@@ -233,19 +267,21 @@ impl<'heap, A: Allocator> CopyPropagationVisitor<'_, 'heap, A> {
233267 ///
234268 /// Returns `Int` if the operand is a constant integer or a local known to hold one,
235269 /// `Place` if it's a non-constant place, or `Other` for operands that can't be simplified.
236- fn try_eval ( & self , operand : Operand < ' heap > ) -> Option < Constant < ' heap > > {
237- if let Operand :: Constant ( constant) = operand {
238- return Some ( constant) ;
270+ fn try_eval ( & self , operand : Operand < ' heap > ) -> Option < KnownValue < ' heap > > {
271+ let place = match operand {
272+ Operand :: Place ( place) => place,
273+ Operand :: Constant ( constant) => return Some ( KnownValue :: Constant ( constant) ) ,
274+ } ;
275+
276+ if !place. projections . is_empty ( ) {
277+ return None ;
239278 }
240279
241- if let Operand :: Place ( place) = operand
242- && place. projections . is_empty ( )
243- && let Some ( & constant) = self . constants . lookup ( place. local )
244- {
245- return Some ( constant) ;
280+ if let Some ( & known) = self . values . lookup ( place. local ) {
281+ return Some ( known) ;
246282 }
247283
248- None
284+ Some ( KnownValue :: Local ( place . local ) )
249285 }
250286}
251287
@@ -262,12 +298,10 @@ impl<'heap, A: Allocator> VisitorMut<'heap> for CopyPropagationVisitor<'_, 'heap
262298 }
263299
264300 fn visit_operand ( & mut self , _: Location , operand : & mut Operand < ' heap > ) -> Self :: Result < ( ) > {
265- if let Operand :: Place ( place) = operand
266- && place. projections . is_empty ( )
267- && let Some ( & constant) = self . constants . lookup ( place. local )
268- {
269- * operand = Operand :: Constant ( constant) ;
270- self . changed = true ;
301+ if let Some ( known) = self . try_eval ( * operand) {
302+ let known: Operand < ' heap > = known. into ( ) ;
303+ self . changed |= known != * operand;
304+ * operand = known;
271305 }
272306
273307 Ok ( ( ) )
@@ -292,16 +326,8 @@ impl<'heap, A: Allocator> VisitorMut<'heap> for CopyPropagationVisitor<'_, 'heap
292326 return Ok ( ( ) ) ;
293327 } ;
294328
295- match load {
296- Operand :: Place ( place) if place. projections . is_empty ( ) => {
297- if let Some ( & constant) = self . constants . lookup ( place. local ) {
298- self . constants . insert ( lhs. local , constant) ;
299- }
300- }
301- Operand :: Place ( _) => { }
302- & mut Operand :: Constant ( constant) => {
303- self . constants . insert ( lhs. local , constant) ;
304- }
329+ if let Some ( known) = self . try_eval ( * load) {
330+ self . values . insert ( lhs. local , known) ;
305331 }
306332
307333 Ok ( ( ) )
0 commit comments