Ver Fonte

Add another optimization

Keep track where data are being loaded to the register, and check they are
indeed being read anywhere or before the same register is updated.
Cesar Rodas há 10 meses atrás
pai
commit
caf65c34b9

+ 1 - 3
utxo/src/filter_expr/compiler/mod.rs

@@ -154,13 +154,11 @@ impl<'a> Compiler<'a> {
 
     pub fn optimize(opcodes: Vec<OpCode>) -> (Vec<OpCode>, bool) {
         vec![
-            //optimizations::assign_unique_register_addresses,
             optimizations::remove_redundant_load_and_mov,
-            optimizations::remove_unused_values,
+            optimizations::remove_unused_load,
             optimizations::calculate_static_values,
             optimizations::remove_useless_jumps,
             optimizations::remove_dead_code,
-            //optimizations::move_load_top,
         ]
         .into_iter()
         .fold((opcodes, false), |(opcodes, has_optimized), f| {

+ 0 - 70
utxo/src/filter_expr/compiler/optimizations/assign_unique_register_address.rs

@@ -1,70 +0,0 @@
-use crate::filter_expr::{opcode::OpCode, Register};
-use std::collections::HashMap;
-
-/// When code is optimized, it is possible that a few registers are being LOADED and not read
-/// and it is not caught by `remove_unused_values`. This function will make sure that every
-/// LOADED and LOADED_EXTERNAL register has a unique address.
-pub fn assign_unique_register_addresses(mut opcodes: Vec<OpCode>) -> (Vec<OpCode>, bool) {
-    let mut has_changed = false;
-    let mut counter: usize = 0;
-    let mut register_new_mapping = HashMap::<_, Register>::new();
-    let mut new_mapping =
-        |register_new_mapping: &mut HashMap<Register, Register>, current: &Register| -> Register {
-            counter += 1;
-            if counter != **current {
-                has_changed = true;
-            }
-
-            register_new_mapping.insert(current.clone(), counter.into());
-            counter.into()
-        };
-
-    opcodes.iter_mut().for_each(|opcode| match opcode {
-        OpCode::LOAD(current, _) | OpCode::LOAD_EXTERNAL(current, _) => {
-            *current = new_mapping(&mut register_new_mapping, current);
-        }
-        OpCode::SHL(set, reg1, _)
-        | OpCode::SHR(set, reg1, _)
-        | OpCode::NOT(set, reg1)
-        | OpCode::CPY(set, reg1)
-        | OpCode::NEG(set, reg1)
-        | OpCode::MOV(set, reg1) => {
-            *reg1 = register_new_mapping[reg1].clone();
-
-            if let Some(new_set) = register_new_mapping.get(set) {
-                *set = new_set.clone()
-            } else {
-                *set = new_mapping(&mut register_new_mapping, set);
-            }
-        }
-        OpCode::HLT(reg1) | OpCode::JEQ(reg1, _) | OpCode::JNE(reg1, _) => {
-            *reg1 = register_new_mapping[reg1].clone();
-        }
-        OpCode::ADD(set, reg1, reg2)
-        | OpCode::MUL(set, reg1, reg2)
-        | OpCode::NE(set, reg1, reg2)
-        | OpCode::GE(set, reg1, reg2)
-        | OpCode::DIV(set, reg1, reg2)
-        | OpCode::LE(set, reg1, reg2)
-        | OpCode::GT(set, reg1, reg2)
-        | OpCode::LT(set, reg1, reg2)
-        | OpCode::DIV(set, reg1, reg2)
-        | OpCode::MOD(set, reg1, reg2)
-        | OpCode::XOR(set, reg1, reg2)
-        | OpCode::SUB(set, reg1, reg2)
-        | OpCode::AND(set, reg1, reg2)
-        | OpCode::EQ(set, reg1, reg2)
-        | OpCode::OR(set, reg1, reg2) => {
-            *reg1 = register_new_mapping[reg1].clone();
-            *reg2 = register_new_mapping[reg2].clone();
-            if let Some(new_set) = register_new_mapping.get(set) {
-                *set = new_set.clone()
-            } else {
-                *set = new_mapping(&mut register_new_mapping, set);
-            }
-        }
-        OpCode::LABEL(_) | OpCode::JMP(_) => {}
-    });
-
-    (opcodes, has_changed)
-}

+ 3 - 6
utxo/src/filter_expr/compiler/optimizations/mod.rs

@@ -1,13 +1,10 @@
-mod assign_unique_register_address;
 mod calculate_static_values;
-mod move_load_top;
 mod remove_dead_code;
 mod remove_redundant_load_and_mov;
-mod remove_unused_values;
+mod remove_unused_load;
 mod remove_useless_jumps;
 
 pub use self::{
-    assign_unique_register_address::*, calculate_static_values::*, move_load_top::*,
-    remove_dead_code::*, remove_redundant_load_and_mov::*, remove_unused_values::*,
-    remove_useless_jumps::*,
+    calculate_static_values::*, remove_dead_code::*, remove_redundant_load_and_mov::*,
+    remove_unused_load::*, remove_useless_jumps::*,
 };

+ 0 - 105
utxo/src/filter_expr/compiler/optimizations/move_load_top.rs

@@ -1,105 +0,0 @@
-use crate::filter_expr::opcode::OpCode;
-use std::collections::HashMap;
-
-/// Move the LOAD to the Top of the program
-///
-/// This optimization will allow the VM to execute all the LOAD operations at the beginning of the
-/// program, once, then clone the initial state of the registers and execute the rest of them.
-///
-/// This optimization works under the assumption that cloning the registers is faster than executing
-/// it
-pub fn move_load_top(mut opcodes: Vec<OpCode>) -> (Vec<OpCode>, bool) {
-    let mut load_new_indexes = HashMap::new();
-    let mut new_index = opcodes.len();
-
-    if opcodes
-        .iter()
-        .fold((true, false), |(is_sorted, non_load_stage), opcode| {
-            let is_load_opcode = match opcode {
-                OpCode::LOAD(reg, _) => {
-                    load_new_indexes.insert(*reg, new_index.into());
-                    new_index += 1;
-                    true
-                }
-                _ => false,
-            };
-
-            if non_load_stage && is_load_opcode {
-                (false, true)
-            } else if !is_load_opcode {
-                (is_sorted, true)
-            } else {
-                (is_sorted, non_load_stage)
-            }
-        })
-        .0
-    {
-        // already sorted, all the LOAD opcodes are at the beginning of the program
-        return (opcodes, false);
-    }
-
-    /// assign new indexes to the static registers and rewrite the entire opcodes to use the new
-    /// addresses
-    opcodes.iter_mut().for_each(|opcode| match opcode {
-        OpCode::LOAD(reg, _) => {
-            *reg = load_new_indexes[reg];
-        }
-        OpCode::LABEL(_) | OpCode::JMP(_) => {}
-        OpCode::LOAD_EXTERNAL(dst, _)
-        | OpCode::HLT(dst)
-        | OpCode::JEQ(dst, _)
-        | OpCode::JNE(dst, _) => {
-            if let Some(new_dst) = load_new_indexes.get(dst) {
-                *dst = *new_dst;
-            }
-        }
-        OpCode::MOV(dst, reg1)
-        | OpCode::SHL(dst, reg1, _)
-        | OpCode::CPY(dst, reg1)
-        | OpCode::SHR(dst, reg1, _)
-        | OpCode::NEG(dst, reg1)
-        | OpCode::NOT(dst, reg1) => {
-            if let Some(value) = load_new_indexes.get(reg1) {
-                *reg1 = *value;
-            }
-            if let Some(new_dst) = load_new_indexes.get(dst) {
-                *dst = *new_dst;
-            }
-        }
-        OpCode::EQ(dst, reg1, reg2)
-        | OpCode::GE(dst, reg1, reg2)
-        | OpCode::GT(dst, reg1, reg2)
-        | OpCode::LE(dst, reg1, reg2)
-        | OpCode::LT(dst, reg1, reg2)
-        | OpCode::NE(dst, reg1, reg2)
-        | OpCode::AND(dst, reg1, reg2)
-        | OpCode::OR(dst, reg1, reg2)
-        | OpCode::XOR(dst, reg1, reg2)
-        | OpCode::ADD(dst, reg1, reg2)
-        | OpCode::SUB(dst, reg1, reg2)
-        | OpCode::MUL(dst, reg1, reg2)
-        | OpCode::DIV(dst, reg1, reg2)
-        | OpCode::MOD(dst, reg1, reg2) => {
-            if let Some(value) = load_new_indexes.get(reg1) {
-                *reg1 = *value;
-            }
-            if let Some(value) = load_new_indexes.get(reg2) {
-                *reg2 = *value;
-            }
-            if let Some(new_dst) = load_new_indexes.get(dst) {
-                *dst = *new_dst;
-            }
-        }
-    });
-
-    // split the opcodes into two parts, the LOAD opcodes and the rest
-    let mut parts: (Vec<_>, Vec<_>) = opcodes
-        .into_iter()
-        .partition(|opcode| matches!(opcode, OpCode::LOAD(_, _)));
-
-    // merge both parts
-    let mut new_opcodes = parts.0;
-    new_opcodes.extend(parts.1);
-
-    (new_opcodes, true)
-}

+ 131 - 0
utxo/src/filter_expr/compiler/optimizations/remove_unused_load.rs

@@ -0,0 +1,131 @@
+use crate::filter_expr::opcode::OpCode;
+use std::collections::HashMap;
+
+/// Remove loaded values that are not read by any opcode. This is useful for reducing the size
+/// of the opcodes to be executed
+pub fn remove_unused_load(mut opcodes: Vec<OpCode>) -> (Vec<OpCode>, bool) {
+    // list of opcodes where the register are accessed
+    let used_registers = opcodes
+        .iter()
+        .enumerate()
+        .map(|(pos, opcode)| match opcode {
+            OpCode::LOAD(_, _)
+            | OpCode::LOAD_EXTERNAL(_, _)
+            | OpCode::LABEL(_)
+            | OpCode::JMP(_) => vec![],
+            OpCode::NOT(_, reg1)
+            | OpCode::CPY(_, reg1)
+            | OpCode::MOV(_, reg1)
+            | OpCode::SHL(_, reg1, _)
+            | OpCode::SHR(_, reg1, _)
+            | OpCode::NEG(_, reg1)
+            | OpCode::HLT(reg1)
+            | OpCode::JNE(reg1, _)
+            | OpCode::JEQ(reg1, _) => {
+                vec![(*reg1, pos)]
+            }
+            OpCode::ADD(_, reg1, reg2)
+            | OpCode::GE(_, reg1, reg2)
+            | OpCode::LE(_, reg1, reg2)
+            | OpCode::MUL(_, reg1, reg2)
+            | OpCode::NE(_, reg1, reg2)
+            | OpCode::DIV(_, reg1, reg2)
+            | OpCode::MOD(_, reg1, reg2)
+            | OpCode::XOR(_, reg1, reg2)
+            | OpCode::SUB(_, reg1, reg2)
+            | OpCode::AND(_, reg1, reg2)
+            | OpCode::OR(_, reg1, reg2)
+            | OpCode::EQ(_, reg1, reg2)
+            | OpCode::GT(_, reg1, reg2)
+            | OpCode::LT(_, reg1, reg2) => {
+                vec![(*reg1, pos), (*reg2, pos)]
+            }
+        })
+        .flatten()
+        .fold(HashMap::new(), |mut acc, (reg, pos)| {
+            acc.entry(reg).or_insert_with(Vec::new).push(pos);
+            acc
+        });
+
+    // a list of registers that are loaded or overwritten
+    let loaded_registers = opcodes
+        .iter()
+        .enumerate()
+        .filter_map(|(pos, opcode)| match opcode {
+            OpCode::LOAD(dst, _) | OpCode::LOAD_EXTERNAL(dst, _) => Some((*dst, pos)),
+            OpCode::ADD(dst, _, _)
+            | OpCode::NOT(dst, _)
+            | OpCode::CPY(dst, _)
+            | OpCode::MOV(dst, _)
+            | OpCode::SHL(dst, _, _)
+            | OpCode::SHR(dst, _, _)
+            | OpCode::NEG(dst, _)
+            | OpCode::GE(dst, _, _)
+            | OpCode::LE(dst, _, _)
+            | OpCode::MUL(dst, _, _)
+            | OpCode::NE(dst, _, _)
+            | OpCode::DIV(dst, _, _)
+            | OpCode::MOD(dst, _, _)
+            | OpCode::XOR(dst, _, _)
+            | OpCode::SUB(dst, _, _)
+            | OpCode::AND(dst, _, _)
+            | OpCode::OR(dst, _, _)
+            | OpCode::EQ(dst, _, _)
+            | OpCode::GT(dst, _, _)
+            | OpCode::LT(dst, _, _) => Some((*dst, pos)),
+            _ => None,
+        })
+        .fold(HashMap::new(), |mut acc, (reg, pos)| {
+            acc.entry(reg).or_insert_with(Vec::new).push(pos);
+            acc
+        });
+
+    let old_total_opcodes = opcodes.len();
+
+    // remove unused registers. If the register has not been read by any opcode, then it can be
+    // removed.
+    let new_opcodes = opcodes
+        .into_iter()
+        .enumerate()
+        .filter_map(|(pos, opcode)| match &opcode {
+            OpCode::LOAD(dst, _) | OpCode::LOAD_EXTERNAL(dst, _) => {
+                // get the address where the register is reloaded.
+                //
+                // The main goal is to check if readed before it is reloaded. If it is not readed
+                // then it is redundant and can be removed.
+                let next_load = loaded_registers
+                    .get(dst)
+                    .expect("Invalid register")
+                    .iter()
+                    .fold(None, |mut next, loaded_at| {
+                        if next.is_none() && *loaded_at > pos {
+                            next = Some(*loaded_at);
+                        }
+                        next
+                    });
+
+                if let Some(readed_at) = used_registers.get(dst) {
+                    if readed_at.iter().any(|readed_at| {
+                        if let Some(next_load) = next_load {
+                            *readed_at < next_load && *readed_at > pos
+                        } else if *readed_at > pos {
+                            true
+                        } else {
+                            false
+                        }
+                    }) {
+                        Some(opcode)
+                    } else {
+                        None
+                    }
+                } else {
+                    None
+                }
+            }
+            _ => Some(opcode),
+        })
+        .collect::<Vec<_>>();
+
+    let new_total_opcodes = new_opcodes.len();
+    (new_opcodes, old_total_opcodes != new_total_opcodes)
+}

+ 0 - 70
utxo/src/filter_expr/compiler/optimizations/remove_unused_values.rs

@@ -1,70 +0,0 @@
-use crate::filter_expr::opcode::OpCode;
-use std::collections::HashMap;
-
-/// Remove loaded values that are not read by any opcode. This is useful for reducing the size
-/// of the opcodes to be executed
-pub fn remove_unused_values(mut opcodes: Vec<OpCode>) -> (Vec<OpCode>, bool) {
-    let used_registers = opcodes
-        .iter()
-        .enumerate()
-        .map(|(pos, opcode)| match opcode {
-            OpCode::LOAD(_, _)
-            | OpCode::LOAD_EXTERNAL(_, _)
-            | OpCode::LABEL(_)
-            | OpCode::JMP(_) => vec![],
-            OpCode::NOT(_, reg1)
-            | OpCode::CPY(_, reg1)
-            | OpCode::MOV(_, reg1)
-            | OpCode::SHL(_, reg1, _)
-            | OpCode::SHR(_, reg1, _)
-            | OpCode::NEG(_, reg1)
-            | OpCode::HLT(reg1)
-            | OpCode::JNE(reg1, _)
-            | OpCode::JEQ(reg1, _) => {
-                vec![(*reg1, pos)]
-            }
-            OpCode::ADD(_, reg1, reg2)
-            | OpCode::GE(_, reg1, reg2)
-            | OpCode::LE(_, reg1, reg2)
-            | OpCode::MUL(_, reg1, reg2)
-            | OpCode::NE(_, reg1, reg2)
-            | OpCode::DIV(_, reg1, reg2)
-            | OpCode::MOD(_, reg1, reg2)
-            | OpCode::XOR(_, reg1, reg2)
-            | OpCode::SUB(_, reg1, reg2)
-            | OpCode::AND(_, reg1, reg2)
-            | OpCode::OR(_, reg1, reg2)
-            | OpCode::EQ(_, reg1, reg2)
-            | OpCode::GT(_, reg1, reg2)
-            | OpCode::LT(_, reg1, reg2) => {
-                vec![(*reg1, pos), (*reg2, pos)]
-            }
-        })
-        .flatten()
-        .fold(HashMap::new(), |mut acc, (reg, pos)| {
-            acc.entry(reg).or_insert_with(Vec::new).push(pos);
-            acc
-        });
-
-    let old_total_opcodes = opcodes.len();
-
-    // remove unused registers. If the register has not been read by any opcode, then it can be
-    // removed.
-    let new_opcodes = opcodes
-        .into_iter()
-        .enumerate()
-        .filter_map(|(pos, opcode)| match &opcode {
-            OpCode::LOAD(dst, _) | OpCode::LOAD_EXTERNAL(dst, _) => {
-                if used_registers.get(dst).is_some() {
-                    return Some(opcode);
-                } else {
-                    None
-                }
-            }
-            _ => Some(opcode),
-        })
-        .collect::<Vec<_>>();
-
-    let new_total_opcodes = new_opcodes.len();
-    (new_opcodes, old_total_opcodes != new_total_opcodes)
-}