From a10fb702d8b030152231540137f153c0acdf1928 Mon Sep 17 00:00:00 2001 From: Einar Rasmussen Date: Sun, 29 Sep 2024 21:11:08 +0200 Subject: [PATCH 1/6] Optimize code by using De Morgan laws --- .../src/cpu/kernel/optimizer.rs | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/evm_arithmetization/src/cpu/kernel/optimizer.rs b/evm_arithmetization/src/cpu/kernel/optimizer.rs index 4890b6f37..48cbc9850 100644 --- a/evm_arithmetization/src/cpu/kernel/optimizer.rs +++ b/evm_arithmetization/src/cpu/kernel/optimizer.rs @@ -27,6 +27,8 @@ fn optimize_asm_once(code: &mut Vec) { remove_swapped_pushes(code); remove_swaps_commutative(code); remove_ignored_values(code); + de_morgan1(code); + de_morgan2(code); } /// Constant propagation. @@ -157,6 +159,48 @@ fn remove_ignored_values(code: &mut Vec) { }); } +/// 1. (not A) and (not B) = not (A or B) +/// [PUSH a, NOT, PUSH b, NOT, AND] -> [PUSH a, PUSH b, OR, NOT] +fn de_morgan1(code: &mut Vec) { + replace_windows(code, |window| { + if let [Push(a), StandardOp(op1), Push(b), StandardOp(op2), StandardOp(op3)] = window + && op1 == "NOT" + && op2 == "NOT" + && op3 == "AND" + { + Some(vec![ + Push(a), + Push(b), + StandardOp("OR".to_string()), + StandardOp("NOT".to_string()), + ]) + } else { + None + } + }); +} + +/// 2. (not A) or (not B) = not (A and B) +/// [PUSH a, NOT, PUSH b, NOT, OR] -> [PUSH a, PUSH b, AND, NOT] +fn de_morgan2(code: &mut Vec) { + replace_windows(code, |window| { + if let [Push(a), StandardOp(op1), Push(b), StandardOp(op2), StandardOp(op3)] = window + && op1 == "NOT" + && op2 == "NOT" + && op3 == "OR" + { + Some(vec![ + Push(a), + Push(b), + StandardOp("AND".to_string()), + StandardOp("NOT".to_string()), + ]) + } else { + None + } + }); +} + /// Like `replace_windows`, but specifically for code, and only makes /// replacements if our cost estimator thinks that the new code is more /// efficient. @@ -285,4 +329,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()), + Push(Literal(8.into())), + StandardOp("NOT".into()), + StandardOp("AND".into()), + ]; + let after = vec![ + Push(Literal(3.into())), + Push(Literal(8.into())), + StandardOp("OR".into()), + StandardOp("NOT".into()), + ]; + assert!(is_code_improved(&before, &after)); + de_morgan1(&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_morgan2(&mut before); + assert_eq!(before, after); + } } From 6425f3ce79c1fb39182a035188ceb121d560de92 Mon Sep 17 00:00:00 2001 From: Einar Rasmussen Date: Sun, 29 Sep 2024 21:24:39 +0200 Subject: [PATCH 2/6] fmt --- evm_arithmetization/src/cpu/kernel/optimizer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/optimizer.rs b/evm_arithmetization/src/cpu/kernel/optimizer.rs index 48cbc9850..d122f7ec0 100644 --- a/evm_arithmetization/src/cpu/kernel/optimizer.rs +++ b/evm_arithmetization/src/cpu/kernel/optimizer.rs @@ -159,7 +159,7 @@ fn remove_ignored_values(code: &mut Vec) { }); } -/// 1. (not A) and (not B) = not (A or B) +/// First law: (not A) and (not B) = not (A or B) /// [PUSH a, NOT, PUSH b, NOT, AND] -> [PUSH a, PUSH b, OR, NOT] fn de_morgan1(code: &mut Vec) { replace_windows(code, |window| { @@ -180,7 +180,7 @@ fn de_morgan1(code: &mut Vec) { }); } -/// 2. (not A) or (not B) = not (A and B) +/// Second law: (not A) or (not B) = not (A and B) /// [PUSH a, NOT, PUSH b, NOT, OR] -> [PUSH a, PUSH b, AND, NOT] fn de_morgan2(code: &mut Vec) { replace_windows(code, |window| { From 111b0d20c4f0500cfb3e6a7cb7f589063c80a53e Mon Sep 17 00:00:00 2001 From: Einar Rasmussen Date: Mon, 30 Sep 2024 12:34:01 +0200 Subject: [PATCH 3/6] support DUP --- .../src/cpu/kernel/optimizer.rs | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/optimizer.rs b/evm_arithmetization/src/cpu/kernel/optimizer.rs index d122f7ec0..ac826d99c 100644 --- a/evm_arithmetization/src/cpu/kernel/optimizer.rs +++ b/evm_arithmetization/src/cpu/kernel/optimizer.rs @@ -159,20 +159,35 @@ fn remove_ignored_values(code: &mut Vec) { }); } +/// 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 +} + /// First law: (not A) and (not B) = not (A or B) /// [PUSH a, NOT, PUSH b, NOT, AND] -> [PUSH a, PUSH b, OR, NOT] fn de_morgan1(code: &mut Vec) { replace_windows(code, |window| { - if let [Push(a), StandardOp(op1), Push(b), StandardOp(op2), StandardOp(op3)] = window + if let [op0, StandardOp(op1), op2, StandardOp(op3), StandardOp(op4)] = window + && is_push_or_dup(&op0) && op1 == "NOT" - && op2 == "NOT" - && op3 == "AND" + && is_push_or_dup(&op2) + && op3 == "NOT" + && op4 == "AND" { Some(vec![ - Push(a), - Push(b), - StandardOp("OR".to_string()), - StandardOp("NOT".to_string()), + op0, + op2, + StandardOp("OR".into()), + StandardOp("NOT".into()), ]) } else { None @@ -184,16 +199,18 @@ fn de_morgan1(code: &mut Vec) { /// [PUSH a, NOT, PUSH b, NOT, OR] -> [PUSH a, PUSH b, AND, NOT] fn de_morgan2(code: &mut Vec) { replace_windows(code, |window| { - if let [Push(a), StandardOp(op1), Push(b), StandardOp(op2), StandardOp(op3)] = window + if let [op0, StandardOp(op1), op2, StandardOp(op3), StandardOp(op4)] = window + && is_push_or_dup(&op0) && op1 == "NOT" - && op2 == "NOT" - && op3 == "OR" + && is_push_or_dup(&op2) + && op3 == "NOT" + && op4 == "OR" { Some(vec![ - Push(a), - Push(b), - StandardOp("AND".to_string()), - StandardOp("NOT".to_string()), + op0, + op2, + StandardOp("AND".into()), + StandardOp("NOT".into()), ]) } else { None @@ -335,13 +352,13 @@ mod tests { let mut before = vec![ Push(Literal(3.into())), StandardOp("NOT".into()), - Push(Literal(8.into())), + StandardOp("DUP1".into()), StandardOp("NOT".into()), StandardOp("AND".into()), ]; let after = vec![ Push(Literal(3.into())), - Push(Literal(8.into())), + StandardOp("DUP1".into()), StandardOp("OR".into()), StandardOp("NOT".into()), ]; From 705cc765f91e2106b9e0fb43a9a91a621225b877 Mon Sep 17 00:00:00 2001 From: Einar Rasmussen Date: Tue, 1 Oct 2024 22:32:32 +0200 Subject: [PATCH 4/6] simplify --- .../src/cpu/kernel/optimizer.rs | 57 +++++++------------ 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/optimizer.rs b/evm_arithmetization/src/cpu/kernel/optimizer.rs index ac826d99c..9f3b6c69e 100644 --- a/evm_arithmetization/src/cpu/kernel/optimizer.rs +++ b/evm_arithmetization/src/cpu/kernel/optimizer.rs @@ -10,6 +10,8 @@ use crate::cpu::kernel::utils::{replace_windows, u256_from_bool}; pub(crate) fn optimize_asm(code: &mut Vec) { // Run the optimizer until nothing changes. + let before = code.len(); + log::info!("Assembly optimizer: Before size: {}.", before); loop { let old_code = code.clone(); optimize_asm_once(code); @@ -17,6 +19,9 @@ pub(crate) fn optimize_asm(code: &mut Vec) { break; } } + let after = code.len(); + log::info!("Assembly optimizer: After size: {}.", after); + panic!(); } /// A single optimization pass. @@ -27,8 +32,7 @@ fn optimize_asm_once(code: &mut Vec) { remove_swapped_pushes(code); remove_swaps_commutative(code); remove_ignored_values(code); - de_morgan1(code); - de_morgan2(code); + de_morgan(code); } /// Constant propagation. @@ -144,15 +148,12 @@ fn remove_swaps_commutative(code: &mut Vec) { // Could be extended to other non-side-effecting operations, e.g. [DUP1, ADD, // POP] -> [POP]. fn remove_ignored_values(code: &mut Vec) { - 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 } @@ -174,42 +175,26 @@ fn is_push_or_dup(op: &Item) -> bool { /// First law: (not A) and (not B) = not (A or B) /// [PUSH a, NOT, PUSH b, NOT, AND] -> [PUSH a, PUSH b, OR, NOT] -fn de_morgan1(code: &mut Vec) { - 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" - { - Some(vec![ - op0, - op2, - StandardOp("OR".into()), - StandardOp("NOT".into()), - ]) - } else { - None - } - }); -} - /// Second law: (not A) or (not B) = not (A and B) /// [PUSH a, NOT, PUSH b, NOT, OR] -> [PUSH a, PUSH b, AND, NOT] -fn de_morgan2(code: &mut Vec) { +/// This also handles `DUP` operations. +fn de_morgan(code: &mut Vec) { 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 == "OR" + && (op4 == "AND" || op4 == "OR") { Some(vec![ op0, op2, - StandardOp("AND".into()), + if op4 == "AND" { + StandardOp("OR".into()) + } else { + StandardOp("AND".into()) + }, StandardOp("NOT".into()), ]) } else { @@ -363,7 +348,7 @@ mod tests { StandardOp("NOT".into()), ]; assert!(is_code_improved(&before, &after)); - de_morgan1(&mut before); + de_morgan(&mut before); assert_eq!(before, after); } @@ -383,7 +368,7 @@ mod tests { StandardOp("NOT".into()), ]; assert!(is_code_improved(&before, &after)); - de_morgan2(&mut before); + de_morgan(&mut before); assert_eq!(before, after); } } From aec07710b1f31472433b6f3c1e1dd02ee14ec53a Mon Sep 17 00:00:00 2001 From: Einar Rasmussen Date: Tue, 1 Oct 2024 22:46:59 +0200 Subject: [PATCH 5/6] feedback --- evm_arithmetization/src/cpu/kernel/optimizer.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/optimizer.rs b/evm_arithmetization/src/cpu/kernel/optimizer.rs index 9f3b6c69e..4275525b8 100644 --- a/evm_arithmetization/src/cpu/kernel/optimizer.rs +++ b/evm_arithmetization/src/cpu/kernel/optimizer.rs @@ -11,7 +11,7 @@ use crate::cpu::kernel::utils::{replace_windows, u256_from_bool}; pub(crate) fn optimize_asm(code: &mut Vec) { // Run the optimizer until nothing changes. let before = code.len(); - log::info!("Assembly optimizer: Before size: {}.", before); + log::trace!("Assembly optimizer: Before size: {}.", before); loop { let old_code = code.clone(); optimize_asm_once(code); @@ -20,8 +20,11 @@ pub(crate) fn optimize_asm(code: &mut Vec) { } } let after = code.len(); - log::info!("Assembly optimizer: After size: {}.", after); - panic!(); + log::trace!( + "Assembly optimizer: After size: {} ({}% reduction).", + after, + (before - after) / before * 100 + ); } /// A single optimization pass. @@ -173,10 +176,10 @@ fn is_push_or_dup(op: &Item) -> bool { false } -/// First law: (not A) and (not B) = not (A or B) -/// [PUSH a, NOT, PUSH b, NOT, AND] -> [PUSH a, PUSH b, OR, NOT] -/// Second law: (not A) or (not B) = not (A and B) -/// [PUSH a, NOT, PUSH b, NOT, OR] -> [PUSH a, PUSH b, AND, NOT] +/// 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) { replace_windows(code, |window| { From b6ebaf99ba953f713b8cd3fdc33b413f231e7841 Mon Sep 17 00:00:00 2001 From: Einar Rasmussen Date: Tue, 1 Oct 2024 23:57:48 +0200 Subject: [PATCH 6/6] reduce logging --- evm_arithmetization/src/cpu/kernel/optimizer.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/evm_arithmetization/src/cpu/kernel/optimizer.rs b/evm_arithmetization/src/cpu/kernel/optimizer.rs index 4275525b8..2fac05e52 100644 --- a/evm_arithmetization/src/cpu/kernel/optimizer.rs +++ b/evm_arithmetization/src/cpu/kernel/optimizer.rs @@ -1,3 +1,5 @@ +use std::cmp::max; + use ethereum_types::U256; use Item::{Push, StandardOp}; use PushTarget::Literal; @@ -11,7 +13,6 @@ use crate::cpu::kernel::utils::{replace_windows, u256_from_bool}; pub(crate) fn optimize_asm(code: &mut Vec) { // Run the optimizer until nothing changes. let before = code.len(); - log::trace!("Assembly optimizer: Before size: {}.", before); loop { let old_code = code.clone(); optimize_asm_once(code); @@ -21,9 +22,10 @@ pub(crate) fn optimize_asm(code: &mut Vec) { } let after = code.len(); log::trace!( - "Assembly optimizer: After size: {} ({}% reduction).", + "Assembly optimizer: {}->{} ({}%).", + before, after, - (before - after) / before * 100 + 100 * after / max(1, before) ); }