1414//! this transformation removes dead stores, i.e., assignments and loads to locals which
1515//! are not live afterwards (or are live only in dead code, making them effectively dead).
1616//! In addition, it also removes self-assignments, i.e., assignments of the form `x = x`.
17+ //! One can also remove only those self-assignments where the definition is in the same block
18+ //! before the self-assign by using `eliminate_all_self_assigns=false`.
1719
1820use crate :: pipeline:: livevar_analysis_processor:: LiveVarAnnotation ;
1921use move_binary_format:: file_format:: CodeOffset ;
@@ -22,6 +24,7 @@ use move_stackless_bytecode::{
2224 function_target:: { FunctionData , FunctionTarget } ,
2325 function_target_pipeline:: { FunctionTargetProcessor , FunctionTargetsHolder } ,
2426 stackless_bytecode:: Bytecode ,
27+ stackless_control_flow_graph:: StacklessControlFlowGraph ,
2528} ;
2629use std:: collections:: { BTreeMap , BTreeSet } ;
2730
@@ -59,19 +62,26 @@ struct ReducedDefUseGraph {
5962
6063impl ReducedDefUseGraph {
6164 /// Get the dead stores that are safe to remove from the function `target`.
62- pub fn dead_stores ( target : & FunctionTarget ) -> BTreeSet < u16 > {
65+ /// If `eliminate_all_self_assigns` is true, all self-assignments are removed.
66+ pub fn dead_stores ( target : & FunctionTarget , eliminate_all_self_assigns : bool ) -> BTreeSet < u16 > {
6367 Self {
6468 children : BTreeMap :: new ( ) ,
6569 parents : BTreeMap :: new ( ) ,
6670 defs_alive : BTreeSet :: new ( ) ,
6771 defs_dead : BTreeSet :: new ( ) ,
6872 }
69- . run_stages ( target)
73+ . run_stages ( target, eliminate_all_self_assigns )
7074 }
7175
7276 /// Run various stages to return the dead stores from `target`.
73- fn run_stages ( mut self , target : & FunctionTarget ) -> BTreeSet < u16 > {
77+ /// If `eliminate_all_self_assigns` is true, all self-assignments are removed.
78+ fn run_stages (
79+ mut self ,
80+ target : & FunctionTarget ,
81+ eliminate_all_self_assigns : bool ,
82+ ) -> BTreeSet < u16 > {
7483 let code = target. get_bytecode ( ) ;
84+ let cfg = StacklessControlFlowGraph :: new_forward ( code) ;
7585 let live_vars = target
7686 . get_annotations ( )
7787 . get :: < LiveVarAnnotation > ( )
@@ -97,8 +107,19 @@ impl ReducedDefUseGraph {
97107 for dead_def_leaf in self . defs_dead . clone ( ) {
98108 self . disconnect_from_parents ( dead_def_leaf) ;
99109 }
100- // Stage 3: Let's disconnect all the self-assignments from the graph and kill them.
110+ // Stage 3: Let's disconnect self-assignments from the graph and kill them
111+ // (conditioned upon `eliminate_all_self_assigns`).
101112 for self_assign in self_assigns {
113+ let eliminate_this_self_assign = Self :: should_eliminate_given_self_assign (
114+ self_assign,
115+ code,
116+ & cfg,
117+ live_vars,
118+ eliminate_all_self_assigns,
119+ ) ;
120+ if !eliminate_this_self_assign {
121+ continue ;
122+ }
102123 let mut parents = self . disconnect_from_parents ( self_assign) ;
103124 let mut children = self . disconnect_from_children ( self_assign) ;
104125 // In case there is a cycle of self-assignments in the graph.
@@ -208,12 +229,55 @@ impl ReducedDefUseGraph {
208229 self . defs_dead . insert ( def) ; // def without a use is dead
209230 }
210231 }
232+
233+ /// Should `self_assign` be eliminated?
234+ fn should_eliminate_given_self_assign (
235+ self_assign : CodeOffset ,
236+ code : & [ Bytecode ] ,
237+ cfg : & StacklessControlFlowGraph ,
238+ live_vars : & LiveVarAnnotation ,
239+ eliminate_all_self_assigns : bool ,
240+ ) -> bool {
241+ if !eliminate_all_self_assigns {
242+ // Eliminate this self assign if the definition for this self-assign is in the same block
243+ // before the self assign.
244+ let block = cfg. enclosing_block ( self_assign) ;
245+ let block_begin_offset = cfg. code_range ( block) . start ;
246+ let self_assign_instr = & code[ self_assign as usize ] ;
247+ let self_assign_temp = self_assign_instr. dests ( ) [ 0 ] ;
248+ // Is `self_assign_temp` live before this block?
249+ let info = live_vars
250+ . get_info_at ( block_begin_offset as CodeOffset )
251+ . before
252+ . get ( & self_assign_temp) ;
253+ match info {
254+ None => true , // must be defined in the block
255+ Some ( live) => !live. usage_offsets ( ) . contains ( & self_assign) ,
256+ }
257+ } else {
258+ true
259+ }
260+ }
211261}
212262
213263/// A processor which performs dead store elimination transformation.
214- pub struct DeadStoreElimination { }
264+ pub struct DeadStoreElimination {
265+ /// If true, eliminate all self-assignments of the form `x = x`.
266+ /// Otherwise, only self assignments where the definition is in the same block
267+ /// before the self-assign are removed.
268+ eliminate_all_self_assigns : bool ,
269+ }
215270
216271impl DeadStoreElimination {
272+ /// If `eliminate_all_self_assigns` is true, all self-assignments are removed.
273+ /// Otherwise, only self assignments where the definition is in the same block
274+ /// before the self-assign are removed.
275+ pub fn new ( eliminate_all_self_assigns : bool ) -> Self {
276+ Self {
277+ eliminate_all_self_assigns,
278+ }
279+ }
280+
217281 /// Transforms the `code` of a function by removing the instructions corresponding to
218282 /// the code offsets contained in `dead_stores`.
219283 ///
@@ -242,7 +306,7 @@ impl FunctionTargetProcessor for DeadStoreElimination {
242306 return data;
243307 }
244308 let target = FunctionTarget :: new ( func_env, & data) ;
245- let dead_stores = ReducedDefUseGraph :: dead_stores ( & target) ;
309+ let dead_stores = ReducedDefUseGraph :: dead_stores ( & target, self . eliminate_all_self_assigns ) ;
246310 let new_code = Self :: transform ( & target, dead_stores) ;
247311 // Note that the file format generator will not include unused locals in the generated code,
248312 // so we don't need to prune unused locals here for various fields of `data` (like `local_types`).
0 commit comments