فهرست منبع

Started working on the optimize()

1. Calculate math operations that can be done
2. Remove unused LOADed values
Cesar Rodas 10 ماه پیش
والد
کامیت
f424663530
2فایلهای تغییر یافته به همراه202 افزوده شده و 16 حذف شده
  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 {