Skip to content

Commit da63178

Browse files
authored
Creating temps for each arg. (aptos-labs#15514)
1 parent 5371540 commit da63178

File tree

245 files changed

+14948
-5400
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

245 files changed

+14948
-5400
lines changed

third_party/move/move-compiler-v2/src/bytecode_generator.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1110,7 +1110,14 @@ impl<'env> Generator<'env> {
11101110
fn gen_arg_list(&mut self, exps: &[Exp]) -> Vec<TempIndex> {
11111111
// If all args are side-effect free, we don't need to force temporary generation
11121112
// to get left-to-right evaluation.
1113-
let with_forced_temp = !exps.iter().all(is_definitely_pure);
1113+
// TODO: after comparison testing, remove depending on the experiment and always
1114+
// have `with_forced_temp` be true.
1115+
let options = self
1116+
.env()
1117+
.get_extension::<Options>()
1118+
.expect("Options is available");
1119+
let with_forced_temp = options.experiment_on(Experiment::RETAIN_TEMPS_FOR_ARGS)
1120+
|| !exps.iter().all(is_definitely_pure);
11141121
let len = exps.len();
11151122
// Generate code with (potentially) forced creation of temporaries for all except last arg.
11161123
let mut args = exps

third_party/move/move-compiler-v2/src/experiments.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,14 @@ pub static EXPERIMENTS: Lazy<BTreeMap<String, Experiment>> = Lazy::new(|| {
276276
description: "Avoid storing to a local during assigns".to_string(),
277277
default: Inherited(Experiment::OPTIMIZE_WAITING_FOR_COMPARE_TESTS.to_string()),
278278
},
279+
Experiment {
280+
name: Experiment::RETAIN_TEMPS_FOR_ARGS.to_string(),
281+
description:
282+
"Create temps for each argument of a function call during stackless bytecode \
283+
generation and retain them until file format bytecode generation"
284+
.to_string(),
285+
default: Inherited(Experiment::OPTIMIZE_WAITING_FOR_COMPARE_TESTS.to_string()),
286+
},
279287
];
280288
experiments
281289
.into_iter()
@@ -316,6 +324,7 @@ impl Experiment {
316324
pub const RECURSIVE_TYPE_CHECK: &'static str = "recursive-type-check";
317325
pub const REFERENCE_SAFETY: &'static str = "reference-safety";
318326
pub const REFERENCE_SAFETY_V3: &'static str = "reference-safety-v3";
327+
pub const RETAIN_TEMPS_FOR_ARGS: &'static str = "retain-temps-for-args";
319328
pub const SEQS_IN_BINOPS_CHECK: &'static str = "seqs-in-binops-check";
320329
pub const SPEC_CHECK: &'static str = "spec-check";
321330
pub const SPEC_REWRITE: &'static str = "spec-rewrite";

third_party/move/move-compiler-v2/src/lib.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ pub fn bytecode_pipeline(env: &GlobalEnv) -> FunctionTargetPipeline {
465465
pipeline.add_processor(Box::new(UnreachableCodeProcessor {}));
466466
pipeline.add_processor(Box::new(UnreachableCodeRemover {}));
467467
pipeline.add_processor(Box::new(LiveVarAnalysisProcessor::new(true)));
468-
pipeline.add_processor(Box::new(DeadStoreElimination {}));
468+
pipeline.add_processor(Box::new(DeadStoreElimination::new(true)));
469469
}
470470

471471
if options.experiment_on(Experiment::VARIABLE_COALESCING) {
@@ -484,7 +484,11 @@ pub fn bytecode_pipeline(env: &GlobalEnv) -> FunctionTargetPipeline {
484484

485485
if options.experiment_on(Experiment::DEAD_CODE_ELIMINATION) {
486486
pipeline.add_processor(Box::new(LiveVarAnalysisProcessor::new(true)));
487-
pipeline.add_processor(Box::new(DeadStoreElimination {}));
487+
// TODO: after comparison testing passes, always call with `eliminate_all_self_assigns` set
488+
// to `false` when instantiating `DeadStoreElimination`.
489+
pipeline.add_processor(Box::new(DeadStoreElimination::new(
490+
!options.experiment_on(Experiment::RETAIN_TEMPS_FOR_ARGS),
491+
)));
488492
}
489493

490494
// Run live var analysis again because it could be invalidated by previous pipeline steps,

third_party/move/move-compiler-v2/src/pipeline/dead_store_elimination.rs

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
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
1820
use crate::pipeline::livevar_analysis_processor::LiveVarAnnotation;
1921
use 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
};
2629
use std::collections::{BTreeMap, BTreeSet};
2730

@@ -59,19 +62,26 @@ struct ReducedDefUseGraph {
5962

6063
impl 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

216271
impl 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

Comments
 (0)