Skip to content

Commit

Permalink
[compiler-v2] Variable window peephole optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
vineethk committed Oct 1, 2024
1 parent bbe2ba8 commit 3c9937d
Show file tree
Hide file tree
Showing 35 changed files with 980 additions and 236 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,26 @@ B0:
9: MutBorrowField[0](OuterStruct.any_field: vector<InnerStruct>)
10: StLoc[2](loc1: &mut vector<InnerStruct>)
11: LdU64(0)
12: CopyLoc[2](loc1: &mut vector<InnerStruct>)
13: FreezeRef
14: VecLen(3)
15: StLoc[3](loc2: u64)
12: StLoc[3](loc2: u64)
13: CopyLoc[2](loc1: &mut vector<InnerStruct>)
14: FreezeRef
15: VecLen(3)
16: StLoc[4](loc3: u64)
B1:
17: CopyLoc[4](loc3: u64)
18: CopyLoc[3](loc2: u64)
17: CopyLoc[3](loc2: u64)
18: CopyLoc[4](loc3: u64)
19: Lt
20: BrFalse(31)
B2:
21: CopyLoc[2](loc1: &mut vector<InnerStruct>)
22: CopyLoc[4](loc3: u64)
22: CopyLoc[3](loc2: u64)
23: VecMutBorrow(3)
24: FreezeRef
25: Call debug::print<InnerStruct>(&InnerStruct)
26: MoveLoc[4](loc3: u64)
26: MoveLoc[3](loc2: u64)
27: LdU64(1)
28: Add
29: StLoc[4](loc3: u64)
29: StLoc[3](loc2: u64)
30: Branch(34)
B3:
31: MoveLoc[2](loc1: &mut vector<InnerStruct>)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Events:
}
{
type: 0x1::transaction_fee::FeeStatement
data: "9c0000000000000007000000000000000400000000000000c83a0200000000000000000000000000"
data: "9c0000000000000006000000000000000400000000000000c83a0200000000000000000000000000"
}mutable inputs after call: local#0: 0
return values: 0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
//! Peephole optimizations assume that the bytecode is valid, and all user-facing
//! error checks have already been performed.
pub mod inefficient_binops;
pub mod inefficient_loads;
pub mod optimizers;
pub mod reducible_pairs;

use inefficient_binops::TransformInefficientBinops;
use inefficient_loads::InefficientLoads;
use move_binary_format::{
control_flow_graph::{ControlFlowGraph, VMControlFlowGraph},
file_format::{Bytecode, CodeOffset, CodeUnit},
};
use optimizers::{BasicBlockOptimizer, FixedWindowProcessor};
use optimizers::{BasicBlockOptimizer, WindowProcessor};
use reducible_pairs::ReduciblePairs;
use std::{collections::BTreeMap, mem};

Expand All @@ -36,8 +36,8 @@ impl BasicBlockOptimizerPipeline {
pub fn default() -> Self {
Self {
optimizers: vec![
Box::new(FixedWindowProcessor::new(ReduciblePairs)),
Box::new(FixedWindowProcessor::new(TransformInefficientBinops)),
Box::new(WindowProcessor::new(ReduciblePairs)),
Box::new(WindowProcessor::new(InefficientLoads)),
],
}
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

//! This module contains a window peephole optimizer for the Move bytecode.
//! As with all peephole optimizers here, it assumes that the bytecode is valid.
//!
//! This optimizer addresses a commonly appearing pattern when involving loads.
//!
//! The pattern is:
//! 1. Load a constant into the stack.
//! 2. Store the constant into a local `u`.
//! 3. A (possibly empty) sequence of instructions that do not involve `u`,
//! which we name `sequence`. Currently, the only instructions that can
//! involve `u` are: `CopyLoc`, `MoveLoc`, `StLoc`, `ImmBorrowLoc`,
//! and `MutBorrowLoc`.
//! 4. A `MoveLoc` of `u`.
//!
//! This pattern can be replaced with:
//! 1. `sequence`.
//! 2. Load the constant into the stack.
//!
//! This transformation leaves the stack in the same state.
//! The local `u` in the original code has been moved from, so later code
//! cannot use it without a subsequent store.
//! So, skipping the store to `u` is safe.
use crate::file_format_generator::peephole_optimizer::optimizers::WindowOptimizer;
use move_binary_format::file_format::Bytecode;

/// An optimizer for inefficient loads.
pub struct InefficientLoads;

impl InefficientLoads {
// We need at least 3 instructions, corresponding to points 1, 2, and 4 in the pattern
// described in the module documentation (at the top of the file).
const MIN_WINDOW_SIZE: usize = 3;
}

impl WindowOptimizer for InefficientLoads {
fn optimize_window(&self, window: &[Bytecode]) -> Option<(Vec<Bytecode>, usize)> {
use Bytecode::*;
if window.len() < Self::MIN_WINDOW_SIZE {
return None;
}
// Load and Store a constant into `u`.
let u = match (&window[0], &window[1]) {
(
LdU8(_) | LdU16(_) | LdU32(_) | LdU64(_) | LdU128(_) | LdU256(_) | LdConst(_)
| LdTrue | LdFalse,
StLoc(u),
) => *u,
_ => return None,
};
for (index, instr) in window[2..].iter().enumerate() {
match instr {
CopyLoc(v) | StLoc(v) | ImmBorrowLoc(v) | MutBorrowLoc(v) if u == *v => {
// We have encountered an instruction that involves `u`.
return None;
},
MoveLoc(v) if u == *v => {
// We have reached the end of the pattern (point 4 in the module documentation).
let sequence = &window[2..index + 2];
let load_constant = &window[0..1];
return Some((
[sequence, load_constant].concat(),
index + Self::MIN_WINDOW_SIZE,
));
},
_ => {
// Instruction that does not involve `u`, including `MoveLoc` of a different local.
},
}
}
// The full pattern was not found.
None
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,20 @@ pub trait BasicBlockOptimizer {
fn optimize(&self, block: &[Bytecode]) -> Vec<Bytecode>;
}

/// An optimizer for a fixed window of bytecode.
/// The fixed window can be assumed to be within a basic block.
pub trait FixedWindowOptimizer {
/// The fixed window size for this optimizer.
fn fixed_window_size(&self) -> usize;

/// Given a fixed `window` of bytecode of size `self.fixed_window_size()`,
/// optionally return its optimized version.
/// An optimizer for a window of bytecode within a basic block.
/// The window is always a suffix of a basic block.
pub trait WindowOptimizer {
/// Given a `window` of bytecode, return a tuple containing:
/// 1. an optimized version of a non-empty prefix of the `window`.
/// 2. size of this prefix (should be non-zero).
/// If `None` is returned, the `window` is not optimized.
fn optimize_fixed_window(&self, window: &[Bytecode]) -> Option<Vec<Bytecode>>;
fn optimize_window(&self, window: &[Bytecode]) -> Option<(Vec<Bytecode>, usize)>;
}

/// A processor to perform fixed window optimizations of a particular style on a basic block.
pub struct FixedWindowProcessor<T: FixedWindowOptimizer>(T);
/// A processor to perform window optimizations of a particular style on a basic block.
pub struct WindowProcessor<T: WindowOptimizer>(T);

impl<T: FixedWindowOptimizer> BasicBlockOptimizer for FixedWindowProcessor<T> {
impl<T: WindowOptimizer> BasicBlockOptimizer for WindowProcessor<T> {
fn optimize(&self, block: &[Bytecode]) -> Vec<Bytecode> {
let mut old_block = block.to_vec();
// Run single passes until code stops changing.
Expand All @@ -37,30 +35,24 @@ impl<T: FixedWindowOptimizer> BasicBlockOptimizer for FixedWindowProcessor<T> {
}
}

impl<T: FixedWindowOptimizer> FixedWindowProcessor<T> {
/// Create a new `FixedWindowProcessor` with the given `optimizer`.
impl<T: WindowOptimizer> WindowProcessor<T> {
/// Create a new `WindowProcessor` with the given `optimizer`.
pub fn new(optimizer: T) -> Self {
Self(optimizer)
}

/// Run a single pass of fixed window peephole optimization on the given basic `block`.
/// Run a single pass of the window peephole optimization on the given basic `block`.
/// If the block cannot be optimized further, return `None`.
fn optimize_single_pass(&self, block: &[Bytecode]) -> Option<Vec<Bytecode>> {
let window_size = self.0.fixed_window_size();
let mut changed = false;
let mut new_block: Vec<Bytecode> = vec![];
let mut left = 0;
while left < block.len() {
let right = left + window_size;
if right > block.len() {
// At the end, not enough instructions to form a fixed window.
new_block.extend(block[left..].to_vec());
break;
}
let window = &block[left..right];
if let Some(optimized_window) = self.0.optimize_fixed_window(window) {
let window = &block[left..];
if let Some((optimized_window, consumed)) = self.0.optimize_window(window) {
debug_assert!(consumed != 0);
new_block.extend(optimized_window);
left = right;
left += consumed;
changed = true;
} else {
new_block.push(block[left].clone());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

//! This module contains a fixed window peephole optimizer for the Move bytecode.
//! This module contains a window peephole optimizer for the Move bytecode.
//! As with all peephole optimizers here, it assumes that the bytecode is valid.
//!
//! We consider fixed windows of size 2 for this optimizer.
Expand Down Expand Up @@ -39,31 +39,35 @@
//! Finally, note that fixed window optimizations are performed on windows within a basic
//! block, not spanning across multiple basic blocks.
use crate::file_format_generator::peephole_optimizer::optimizers::FixedWindowOptimizer;
use crate::file_format_generator::peephole_optimizer::optimizers::WindowOptimizer;
use move_binary_format::file_format::Bytecode;

pub struct ReduciblePairs;

impl FixedWindowOptimizer for ReduciblePairs {
fn fixed_window_size(&self) -> usize {
2
}
impl ReduciblePairs {
const WINDOW_SIZE: usize = 2;
}

fn optimize_fixed_window(&self, window: &[Bytecode]) -> Option<Vec<Bytecode>> {
impl WindowOptimizer for ReduciblePairs {
fn optimize_window(&self, window: &[Bytecode]) -> Option<(Vec<Bytecode>, usize)> {
use Bytecode::*;
if window.len() < Self::WINDOW_SIZE {
return None;
}
// See module documentation for the reasoning behind these optimizations.
match (&window[0], &window[1]) {
let optimized = match (&window[0], &window[1]) {
(StLoc(u), MoveLoc(v)) | (CopyLoc(u), StLoc(v)) | (MoveLoc(u), StLoc(v))
if *u == *v =>
{
Some(vec![])
vec![]
},
(CopyLoc(_), Pop) => Some(vec![]),
(LdTrue, BrTrue(target)) | (LdFalse, BrFalse(target)) => Some(vec![Branch(*target)]),
(LdTrue, BrFalse(_)) | (LdFalse, BrTrue(_)) => Some(vec![]),
(Not, BrFalse(target)) => Some(vec![BrTrue(*target)]),
(Not, BrTrue(target)) => Some(vec![BrFalse(*target)]),
_ => None,
}
(CopyLoc(_), Pop) => vec![],
(LdTrue, BrTrue(target)) | (LdFalse, BrFalse(target)) => vec![Branch(*target)],
(LdTrue, BrFalse(_)) | (LdFalse, BrTrue(_)) => vec![],
(Not, BrFalse(target)) => vec![BrTrue(*target)],
(Not, BrTrue(target)) => vec![BrFalse(*target)],

Check warning on line 68 in third_party/move/move-compiler-v2/src/file_format_generator/peephole_optimizer/reducible_pairs.rs

View check run for this annotation

Codecov / codecov/patch

third_party/move/move-compiler-v2/src/file_format_generator/peephole_optimizer/reducible_pairs.rs#L68

Added line #L68 was not covered by tests
_ => return None,
};
Some((optimized, Self::WINDOW_SIZE))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,14 @@ B0:
3: Pack[1](S)
4: StLoc[0](loc0: S)
5: MutBorrowLoc[0](loc0: S)
6: LdU64(42)
7: StLoc[1](loc1: u64)
8: MutBorrowField[0](S.g: T)
9: MutBorrowField[1](T.h: u64)
10: StLoc[3](loc3: &mut u64)
11: MoveLoc[1](loc1: u64)
12: MoveLoc[3](loc3: &mut u64)
13: WriteRef
14: MoveLoc[0](loc0: S)
15: Ret
6: MutBorrowField[0](S.g: T)
7: MutBorrowField[1](T.h: u64)
8: StLoc[3](loc3: &mut u64)
9: LdU64(42)
10: MoveLoc[3](loc3: &mut u64)
11: WriteRef
12: MoveLoc[0](loc0: S)
13: Ret
}
write_param(Arg0: &mut S) /* def_idx: 4 */ {
B0:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,13 @@ L3: loc1: &mut u64
B0:
0: MoveLoc[0](Arg0: address)
1: MutBorrowGlobal[0](R)
2: LdU64(2)
3: StLoc[1](Arg1: u64)
4: MutBorrowField[0](R.f: u64)
5: StLoc[3](loc1: &mut u64)
6: MoveLoc[1](Arg1: u64)
7: MoveLoc[3](loc1: &mut u64)
8: WriteRef
9: LdU64(9)
10: Ret
2: MutBorrowField[0](R.f: u64)
3: StLoc[3](loc1: &mut u64)
4: LdU64(2)
5: MoveLoc[3](loc1: &mut u64)
6: WriteRef
7: LdU64(9)
8: Ret
}
}
============ bytecode verification succeeded ========
Loading

0 comments on commit 3c9937d

Please sign in to comment.