Skip to content

Commit

Permalink
Optimize code by using De Morgan laws (#670)
Browse files Browse the repository at this point in the history
  • Loading branch information
einar-polygon authored Oct 10, 2024
1 parent e859d84 commit 413f2dd
Showing 1 changed file with 99 additions and 8 deletions.
107 changes: 99 additions & 8 deletions evm_arithmetization/src/cpu/kernel/optimizer.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::cmp::max;

use ethereum_types::U256;
use Item::{Push, StandardOp};
use PushTarget::Literal;
Expand All @@ -10,13 +12,21 @@ use crate::cpu::kernel::utils::{replace_windows, u256_from_bool};

pub(crate) fn optimize_asm(code: &mut Vec<Item>) {
// Run the optimizer until nothing changes.
let before = code.len();
loop {
let old_code = code.clone();
optimize_asm_once(code);
if code == &old_code {
break;
}
}
let after = code.len();
log::trace!(
"Assembly optimizer: {}->{} ({}%).",
before,
after,
100 * after / max(1, before)
);
}

/// A single optimization pass.
Expand All @@ -27,6 +37,7 @@ fn optimize_asm_once(code: &mut Vec<Item>) {
remove_swapped_pushes(code);
remove_swaps_commutative(code);
remove_ignored_values(code);
de_morgan(code);
}

/// Constant propagation.
Expand Down Expand Up @@ -142,15 +153,55 @@ fn remove_swaps_commutative(code: &mut Vec<Item>) {
// Could be extended to other non-side-effecting operations, e.g. [DUP1, ADD,
// POP] -> [POP].
fn remove_ignored_values(code: &mut Vec<Item>) {
replace_windows(code, |[a, b]| {
if let StandardOp(pop) = b
&& &pop == "POP"
replace_windows(code, |window| {
if let [a, StandardOp(pop)] = window
&& is_push_or_dup(&a)
&& pop == "POP"
{
match a {
Push(_) => Some(vec![]),
StandardOp(dup) if dup.starts_with("DUP") => Some(vec![]),
_ => None,
}
Some(vec![])
} else {
None
}
});
}

/// Helper predicate for the De Morgan rules.
fn is_push_or_dup(op: &Item) -> bool {
if matches!(&op, &Push(_)) {
return true;
};
if let StandardOp(inner) = op
&& inner.starts_with("DUP")
{
return true;
}
false
}

/// De Morgan's First Law: `(not A) and (not B) = not (A or B)`.
/// e.g. `[PUSH a, NOT, PUSH b, NOT, AND] -> [PUSH a, PUSH b, OR, NOT]`.
/// De Morgan's Second Law: `(not A) or (not B) = not (A and B)`.
/// e.g. `[PUSH a, NOT, PUSH b, NOT, OR] -> [PUSH a, PUSH b, AND, NOT]`.
/// This also handles `DUP` operations.
fn de_morgan(code: &mut Vec<Item>) {
replace_windows(code, |window| {
if let [op0, StandardOp(op1), op2, StandardOp(op3), StandardOp(op4)] = window
&& is_push_or_dup(&op0)
&& op1 == "NOT"
&& is_push_or_dup(&op2)
&& op3 == "NOT"
&& (op4 == "AND" || op4 == "OR")
{
Some(vec![
op0,
op2,
if op4 == "AND" {
StandardOp("OR".into())
} else {
StandardOp("AND".into())
},
StandardOp("NOT".into()),
])
} else {
None
}
Expand Down Expand Up @@ -285,4 +336,44 @@ mod tests {
remove_ignored_values(&mut code);
assert_eq!(code, vec![]);
}

#[test]
fn test_demorgan1() {
let mut before = vec![
Push(Literal(3.into())),
StandardOp("NOT".into()),
StandardOp("DUP1".into()),
StandardOp("NOT".into()),
StandardOp("AND".into()),
];
let after = vec![
Push(Literal(3.into())),
StandardOp("DUP1".into()),
StandardOp("OR".into()),
StandardOp("NOT".into()),
];
assert!(is_code_improved(&before, &after));
de_morgan(&mut before);
assert_eq!(before, after);
}

#[test]
fn test_demorgan2() {
let mut before = vec![
Push(Literal(3.into())),
StandardOp("NOT".into()),
Push(Literal(8.into())),
StandardOp("NOT".into()),
StandardOp("OR".into()),
];
let after = vec![
Push(Literal(3.into())),
Push(Literal(8.into())),
StandardOp("AND".into()),
StandardOp("NOT".into()),
];
assert!(is_code_improved(&before, &after));
de_morgan(&mut before);
assert_eq!(before, after);
}
}

0 comments on commit 413f2dd

Please sign in to comment.