|
@@ -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();
|
|
|
|