Prechádzať zdrojové kódy

Started working on the optimize()

1. Calculate math operations that can be done
2. Remove unused LOADed values
Cesar Rodas 9 mesiacov pred
rodič
commit
f424663530
2 zmenil súbory, kde vykonal 202 pridanie a 16 odobranie
  1. 190 15
      utxo/src/filter_expr/filter.rs
  2. 12 1
      utxo/src/filter_expr/value.rs

+ 190 - 15
utxo/src/filter_expr/filter.rs

@@ -16,10 +16,10 @@ pub struct Filter {
     query: Query,
     /// List of external variables
     variables: Vec<Variable>,
-    /// Debug op-codes, with the unresolved labels.
-    raw_opcodes: Vec<OpCode>,
-    /// The list of opcodes that make up the program
+    /// Opcodes with human readable labels
     opcodes: Vec<OpCode>,
+    /// The list of opcodes that make up the program, the labels has been converted into addresses
+    opcodes_to_execute: Vec<OpCode>,
     /// If the program has some boilerplate that can be skipped. This is non-zero when the initial
     /// program has been executed at compile time, and the register has been populated to
     /// `initial_register`. Everytime a new Runtime is created, instead of executing the program,
@@ -44,7 +44,7 @@ impl Filter {
 
         Ok(Self {
             query,
-            raw_opcodes: opcodes.clone(),
+            opcodes: opcodes.clone(),
             variables: opcodes
                 .iter()
                 .filter_map(|x| match x {
@@ -52,20 +52,186 @@ impl Filter {
                     _ => None,
                 })
                 .collect(),
-            opcodes: Compiler::resolve_label_to_addr(opcodes)?,
+            opcodes_to_execute: Compiler::resolve_label_to_addr(opcodes)?,
             start_at: 0.into(),
             initial_register: vec![],
         })
     }
 
-    /// Optimize the raw opcode to remove any unnecessary instructions
-    pub fn optimize(mut self) -> Self {
-        todo!()
+    /// Executes operations that can be done at compile time, and sets the initial_register to the
+    /// result. This is useful for optimizing the program, by executing at `compile` time as
+    /// much as possible.
+    fn calculate_static_values(&mut self) -> bool {
+        let mut register = HashMap::new();
+        let mut has_changed = false;
+
+        self.opcodes.iter_mut().for_each(|opcode| {
+            match &opcode {
+                OpCode::LOAD(dst, value) => {
+                    register.insert(*dst, value.clone());
+                }
+                OpCode::MUL(dst, reg1, reg2)
+                | OpCode::SUB(dst, reg1, reg2)
+                | OpCode::DIV(dst, reg1, reg2)
+                | OpCode::ADD(dst, reg1, reg2) => {
+                    let number1 = match register.get(reg1) {
+                        Some(Value::Number(number)) => *number,
+                        _ => return,
+                    };
+                    let number2 = match register.get(reg2) {
+                        Some(Value::Number(number)) => *number,
+                        _ => return,
+                    };
+
+                    if let Some(calculated_value) = match opcode {
+                        OpCode::ADD(_, _, _) => number1.checked_add(number2).map(Value::Number),
+                        OpCode::MUL(_, _, _) => number1.checked_mul(number2).map(Value::Number),
+                        OpCode::SUB(_, _, _) => number1.checked_sub(number2).map(Value::Number),
+                        OpCode::DIV(_, _, _) => number1.checked_div(number2).map(Value::Number),
+                        _ => None,
+                    } {
+                        register.insert(*dst, calculated_value.clone());
+                        *opcode = OpCode::LOAD(*dst, calculated_value);
+                        has_changed = true;
+                    }
+                }
+                _ => {}
+            };
+        });
+        has_changed
+    }
+
+    /// 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.
+    fn assign_unique_register_addresses(&mut self) -> bool {
+        let mut has_changed = false;
+        let mut counter: usize = self.opcodes.len();
+
+        let mut register_new_mapping = HashMap::new();
+
+        self.opcodes.iter_mut().for_each(|opcode| match opcode {
+            OpCode::LOAD(current, _) | OpCode::LOAD_EXTERNAL(current, _) => {
+                counter += 1;
+                if counter != **current {
+                    has_changed = true;
+                }
+
+                register_new_mapping.insert(**current, counter.into());
+                *current = register_new_mapping[current];
+            }
+            OpCode::SHL(_, reg1, _)
+            | OpCode::SHR(_, reg1, _)
+            | OpCode::NOT(_, reg1)
+            | OpCode::CPY(_, reg1)
+            | OpCode::MOV(_, reg1)
+            | OpCode::JEQ(reg1, _)
+            | OpCode::JNE(reg1, _) => {
+                *reg1 = register_new_mapping[reg1];
+            }
+            OpCode::ADD(_, reg1, reg2)
+            | OpCode::MUL(_, reg1, reg2)
+            | OpCode::DIV(_, reg1, reg2)
+            | OpCode::MOD(_, reg1, reg2)
+            | OpCode::XOR(_, reg1, reg2)
+            | OpCode::SUB(_, reg1, reg2)
+            | OpCode::AND(_, reg1, reg2)
+            | OpCode::EQ(_, reg1, reg2)
+            | OpCode::OR(_, reg1, reg2) => {
+                *reg1 = register_new_mapping[reg1];
+                *reg2 = register_new_mapping[reg2];
+            }
+            _ => {}
+        });
+
+        has_changed
+    }
+
+    /// Remove loaded values that are not read by any opcode. This is useful for reducing the size
+    /// of the opcodes to be executed
+    fn remove_unused_values(mut self) -> (Self, bool) {
+        let used_registers = self
+            .opcodes
+            .iter()
+            .enumerate()
+            .map(|(pos, opcode)| match opcode {
+                OpCode::NOT(_, reg1)
+                | OpCode::CPY(_, reg1)
+                | OpCode::MOV(_, reg1)
+                | OpCode::JEQ(reg1, _)
+                | OpCode::SHL(_, reg1, _)
+                | OpCode::SHR(_, reg1, _)
+                | OpCode::JEQ(reg1, _) => {
+                    vec![(*reg1, pos)]
+                }
+                OpCode::ADD(_, reg1, reg2)
+                | OpCode::MUL(_, 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)]
+                }
+                _ => vec![],
+            })
+            .flatten()
+            .fold(HashMap::new(), |mut acc, (reg, pos)| {
+                acc.entry(reg).or_insert_with(Vec::new).push(pos);
+                acc
+            });
+
+        let total_opcodes = self.opcodes.len();
+
+        // remove unused registers. If the register has not been read by any opcode, then it can be
+        // removed.
+        let new_opcodes = self
+            .opcodes
+            .into_iter()
+            .enumerate()
+            .filter_map(|(pos, opcode)| match &opcode {
+                OpCode::LOAD(dst, _) => {
+                    if used_registers.get(dst).is_some() {
+                        return Some(opcode);
+                    } else {
+                        None
+                    }
+                }
+                _ => Some(opcode),
+            })
+            .collect();
+
+        self.opcodes = new_opcodes;
+        let new_total_opcodes = self.opcodes.len();
+
+        (self, total_opcodes != new_total_opcodes)
+    }
+
+    /// Attempts to optiomize the `raw_opcodes` inside the Filter. Returns a tuple with the new
+    /// self with the optimized opcodes, and a boolean indicating if the program was optimized.
+    pub fn optimize_round(mut self) -> (Self, bool) {
+        let has_calculated_static_values = self.calculate_static_values();
+        let has_changed_register_addresses = self.assign_unique_register_addresses();
+        let (mut new_self, has_removed_unused_values) = self.remove_unused_values();
+
+        new_self.opcodes_to_execute =
+            Compiler::resolve_label_to_addr(new_self.opcodes.clone()).unwrap();
+
+        (
+            new_self,
+            has_calculated_static_values
+                || has_removed_unused_values
+                || has_changed_register_addresses,
+        )
     }
 
     /// Returns a human readable version of the compiled program (generated op-codes)
     pub fn debug(&self) -> String {
-        self.raw_opcodes
+        self.opcodes
             .iter()
             .map(|x| match x {
                 OpCode::HLT(_) | OpCode::LABEL(_) => x.to_string(),
@@ -76,7 +242,7 @@ impl Filter {
     }
 
     pub fn dump(&self) -> String {
-        self.opcodes
+        self.opcodes_to_execute
             .iter()
             .enumerate()
             .map(|(pos, opcode)| format!("{}: {}", pos, opcode.to_string()))
@@ -90,7 +256,7 @@ impl Filter {
     ) -> Result<Value, Error> {
         execute(
             external_variables,
-            &self.opcodes,
+            &self.opcodes_to_execute,
             self.initial_register.clone(),
             self.start_at,
         )
@@ -107,20 +273,29 @@ mod test {
 
     #[test]
     fn parse() {
-        let x = Filter::new(
+        let mut x = Filter::new(
             r#"
             WHERE
-                $foo = 3 + 2 * 3
+                $foo = 3 + 2 * 4 / 2 * 298210
         "#,
         )
         .unwrap();
-        println!("{}", x.dump());
+        println!("{}\n", x.dump());
+
+        loop {
+            let (new_x, is_done) = x.optimize_round();
+            println!("{}\n", new_x.dump());
+            x = new_x;
+            if !is_done {
+                break;
+            }
+        }
 
         let external_variables_1 = vec![("foo".into(), ValueOrRef::Value(0.into()))]
             .into_iter()
             .collect();
 
-        let external_variables_2 = vec![("foo".into(), ValueOrRef::Value(9.into()))]
+        let external_variables_2 = vec![("foo".into(), ValueOrRef::Value(1192843.into()))]
             .into_iter()
             .collect();
 

+ 12 - 1
utxo/src/filter_expr/value.rs

@@ -108,7 +108,7 @@ impl CheckedAdd for Value {
     }
 }
 
-#[derive(Debug, PartialEq, PartialOrd)]
+#[derive(Debug, PartialOrd)]
 /// Value or reference to a value.
 ///
 /// A reference to a value is being used to avoid cloning from the source code to the registers,
@@ -118,6 +118,17 @@ pub enum ValueOrRef<'a> {
     Value(Value),
 }
 
+impl PartialEq for ValueOrRef<'_> {
+    fn eq(&self, other: &Self) -> bool {
+        match (self, other) {
+            (ValueOrRef::Ref(a), ValueOrRef::Ref(b)) => a == b,
+            (ValueOrRef::Value(a), ValueOrRef::Value(b)) => a == b,
+            (ValueOrRef::Ref(a), ValueOrRef::Value(b)) => *a == b,
+            (ValueOrRef::Value(a), ValueOrRef::Ref(b)) => a == *b,
+        }
+    }
+}
+
 impl<'a> Clone for ValueOrRef<'a> {
     fn clone(&self) -> Self {
         match self {