diff --git a/valuescript_compiler/src/function_compiler.rs b/valuescript_compiler/src/function_compiler.rs index e98204b..6216091 100644 --- a/valuescript_compiler/src/function_compiler.rs +++ b/valuescript_compiler/src/function_compiler.rs @@ -214,7 +214,10 @@ impl FunctionCompiler { } pub fn release_reg(&mut self, reg: &Register) { - self.reg_allocator.release(reg); + // Note: We no longer release registers back into the name allocator. See + // `NameAllocator::release` for more information. + // self.reg_allocator.release(reg); + self.current.body.push(FnLine::Release(reg.clone())); } diff --git a/valuescript_compiler/src/name_allocator.rs b/valuescript_compiler/src/name_allocator.rs index 84abb01..39764fb 100644 --- a/valuescript_compiler/src/name_allocator.rs +++ b/valuescript_compiler/src/name_allocator.rs @@ -143,6 +143,11 @@ impl RegAllocator { Register::named(name) } + // TODO: We used to release names back into the allocator a lot. However, the optimizer now + // extends lifetimes, which means re-purposing a register is no longer safe. The optimizer needs + // to do its own analysis to restore register re-use, because we're now using way too many. + // Anyway, marking this as dead code for now but maybe it should just be removed. + #[allow(dead_code)] pub fn release(&mut self, reg: &Register) { match reg.is_named() { true => self.alloc.release(®.name), diff --git a/valuescript_compiler/src/optimization/kal.rs b/valuescript_compiler/src/optimization/kal.rs new file mode 100644 index 0000000..c5fae25 --- /dev/null +++ b/valuescript_compiler/src/optimization/kal.rs @@ -0,0 +1,500 @@ +use num_bigint::BigInt; +use valuescript_vm::{ + operations, + vs_object::VsObject, + vs_value::{ToVal, Val}, +}; + +use std::collections::BTreeMap; + +use crate::{ + asm::{self, Builtin, Number, Pointer, Register, Value}, + instruction::Instruction, +}; + +use super::try_to_kal::TryToKal; + +/** + * Kal: Knowledge about a Val. + * + * This is used by the optimizer to make simplifications. It's a broader and more complex version + * of Val, since every (non-external) Val can be represented as a Kal, and Kal can also represent + * partial information about a Val, such as being a number or being equal to another register. + * + * This is similar to a type system. However, a type system has the constraint of needing to be + * consistent and sensible so the programmer can use it. Kal has the advantage of only being used + * for optimization, so we can do a lot more heuristic things like knowing when an index is within + * the bounds of an array (without needing to nail down exactly when and why we know that in a + * consistent way). It is also 100% mandatory that Kal is always accurate/sound (otherwise we'll + * change program behavior due to believing false things), whereas sometimes type systems (notably + * TypeScript) are not. + */ +#[derive(Clone)] +pub enum Kal { + Unknown, + Void, + Undefined, + Null, + Bool(bool), + Number(Number), + BigInt(BigInt), + String(String), + Array(Box), + Object(Box), + Register(Register), + Pointer(Pointer), + Builtin(Builtin), +} + +impl Default for Kal { + fn default() -> Self { + Kal::Unknown + } +} + +#[derive(Clone)] +pub struct Array { + pub values: Vec, +} + +#[derive(Clone)] +pub struct Object { + pub properties: Vec<(Kal, Kal)>, +} + +impl Kal { + fn visit_kals_mut(&mut self, visit: &mut F) + where + F: FnMut(&mut Kal) -> (), + { + visit(self); + + match self { + Kal::Array(array) => { + for item in &mut array.values { + item.visit_kals_mut(visit); + } + } + Kal::Object(object) => { + for (k, v) in &mut object.properties { + k.visit_kals_mut(visit); + v.visit_kals_mut(visit); + } + } + Kal::Unknown => {} + Kal::Void => {} + Kal::Undefined => {} + Kal::Null => {} + Kal::Bool(..) => {} + Kal::Number(..) => {} + Kal::BigInt(..) => {} + Kal::String(..) => {} + Kal::Register(..) => {} + Kal::Pointer(..) => {} + Kal::Builtin(..) => {} + } + } + + fn from_value(value: &Value) -> Self { + match value { + Value::Void => Kal::Void, + Value::Undefined => Kal::Undefined, + Value::Null => Kal::Null, + Value::Bool(bool) => Kal::Bool(*bool), + Value::Number(Number(x)) => Kal::Number(Number(*x)), + Value::BigInt(bi) => Kal::BigInt(bi.clone()), + Value::String(string) => Kal::String(string.clone()), + Value::Array(array) => Kal::Array(Box::new(Array { + values: array.values.iter().map(Kal::from_value).collect(), + })), + Value::Object(object) => Kal::Object(Box::new(Object { + properties: object + .properties + .iter() + .map(|(k, v)| (Kal::from_value(k), Kal::from_value(v))) + .collect(), + })), + Value::Register(reg) => Kal::Register(reg.clone()), + Value::Pointer(p) => Kal::Pointer(p.clone()), + Value::Builtin(b) => Kal::Builtin(b.clone()), + } + } + + fn to_value(&self) -> Option { + match self { + Kal::Unknown => None, + Kal::Void => Some(Value::Void), + Kal::Undefined => Some(Value::Undefined), + Kal::Null => Some(Value::Null), + Kal::Bool(x) => Some(Value::Bool(*x)), + Kal::Number(Number(x)) => Some(Value::Number(Number(*x))), + Kal::BigInt(x) => Some(Value::BigInt(x.clone())), + Kal::String(x) => Some(Value::String(x.clone())), + Kal::Array(x) => Some(Value::Array(Box::new(asm::Array { + values: { + let mut values = Vec::::new(); + + for k in &x.values { + match k.to_value() { + Some(v) => values.push(v), + None => return None, + } + } + + values + }, + }))), + Kal::Object(x) => Some(Value::Object(Box::new(asm::Object { + properties: { + let mut properties = Vec::<(asm::Value, asm::Value)>::new(); + + for (k, v) in &x.properties { + let k = match k.to_value() { + Some(k) => k, + None => return None, + }; + + let v = match v.to_value() { + Some(v) => v, + None => return None, + }; + + properties.push((k, v)); + } + + properties + }, + }))), + Kal::Register(x) => Some(Value::Register(x.clone())), + Kal::Pointer(x) => Some(Value::Pointer(x.clone())), + Kal::Builtin(x) => Some(Value::Builtin(x.clone())), + } + } + + fn try_to_val(self) -> Option { + Some(match self { + Kal::Unknown => return None, + Kal::Undefined => Val::Undefined, + Kal::Null => Val::Null, + Kal::Bool(b) => b.to_val(), + Kal::Number(Number(n)) => n.to_val(), + Kal::BigInt(n) => n.to_val(), + Kal::String(s) => s.to_val(), + Kal::Array(arr) => { + let mut result = Vec::::new(); + + for value in arr.values { + result.push(value.try_to_val()?); + } + + result.to_val() + } + Kal::Object(obj) => { + let mut string_map = BTreeMap::::new(); + + for (key, value) in obj.properties { + string_map.insert(key.try_to_val()?.to_string(), value.try_to_val()?); + } + + VsObject { + string_map, + symbol_map: Default::default(), + prototype: None, + } + .to_val() + } + + Kal::Void | Kal::Register(..) | Kal::Pointer(..) | Kal::Builtin(..) => { + return None; + } + }) + } +} + +#[derive(Default)] +pub struct FnState { + pub mutable_this_established: bool, + pub registers: BTreeMap, +} + +impl FnState { + fn get_mut(&mut self, reg_name: String) -> &mut Kal { + self.registers.entry(reg_name).or_default() + } + + fn get(&mut self, reg_name: String) -> &Kal { + self.get_mut(reg_name) + } + + pub fn set(&mut self, reg_name: String, kal: Kal) { + *self.get_mut(reg_name.clone()) = kal; + self.handle_reg_changed(®_name); + } + + fn handle_reg_changed(&mut self, changed_reg: &String) { + for kal in self.registers.values_mut() { + kal.visit_kals_mut(&mut |sub_kal| { + if let Kal::Register(reg) = sub_kal { + if reg.name == *changed_reg { + *sub_kal = Kal::Unknown; + } + } + }); + } + } + + pub fn eval_instruction(&mut self, instr: &mut Instruction) { + use Instruction::*; + + match instr { + End => {} + Mov(arg, dst) => { + let arg = self.eval_arg(arg); + self.set(dst.name.clone(), arg); + } + + OpInc(reg) => { + // TODO: Use apply_binary_op? + + let new_value = match self.get(reg.name.clone()) { + Kal::Number(Number(x)) => Kal::Number(Number(x + 1.0)), + Kal::BigInt(x) => Kal::BigInt(x + BigInt::from(1)), + _ => Kal::Unknown, + }; + + self.set(reg.name.clone(), new_value); + } + OpDec(reg) => { + // TODO: Use apply_binary_op? + + let new_value = match self.get(reg.name.clone()) { + Kal::Number(Number(x)) => Kal::Number(Number(x - 1.0)), + Kal::BigInt(x) => Kal::BigInt(x - BigInt::from(1)), + _ => Kal::Unknown, + }; + + self.set(reg.name.clone(), new_value); + } + + OpNot(a1, dst) => self.apply_unary_op(a1, dst, operations::op_not), + OpBitNot(a1, dst) => self.apply_unary_op(a1, dst, operations::op_bit_not), + TypeOf(a1, dst) => self.apply_unary_op(a1, dst, operations::op_typeof), + UnaryPlus(a1, dst) => self.apply_unary_op(a1, dst, operations::op_unary_plus), + UnaryMinus(a1, dst) => self.apply_unary_op(a1, dst, operations::op_unary_minus), + Import(a1, dst) | ImportStar(a1, dst) | Cat(a1, dst) => { + self.eval_arg(a1); + + // TODO: cat + self.set(dst.name.clone(), Kal::Unknown); + } + + Yield(a1, dst) | YieldStar(a1, dst) => { + self.eval_arg(a1); + self.set(dst.name.clone(), Kal::Unknown); + } + + Throw(a1) => { + self.eval_arg(a1); + } + + OpPlus(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_plus), + OpMinus(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_minus), + OpMul(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_mul), + OpDiv(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_div), + OpMod(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_mod), + OpExp(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_exp), + OpEq(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_eq), + OpNe(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_ne), + OpTripleEq(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_triple_eq), + OpTripleNe(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_triple_ne), + OpAnd(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_and), + OpOr(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_or), + OpLess(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_less), + OpLessEq(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_less_eq), + OpGreater(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_greater), + OpGreaterEq(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_greater_eq), + OpNullishCoalesce(a1, a2, dst) => { + self.apply_binary_op(a1, a2, dst, operations::op_nullish_coalesce) + } + OpOptionalChain(a1, a2, dst) => { + self.eval_arg(a1); + self.eval_arg(a2); + + // self.apply_binary_op(a1, a2, dst, operations::op_optional_chain) + // TODO: op_optional_chain takes mut lhs to optimize, but breaks this pattern + self.set(dst.name.clone(), Kal::Unknown); + } + OpBitAnd(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_bit_and), + OpBitOr(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_bit_or), + OpBitXor(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_bit_xor), + OpLeftShift(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_left_shift), + OpRightShift(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_right_shift), + OpRightShiftUnsigned(a1, a2, dst) => { + self.apply_binary_op(a1, a2, dst, operations::op_right_shift_unsigned) + } + InstanceOf(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_instance_of), + In(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_in), + + Call(a1, a2, dst) + | Bind(a1, a2, dst) + | Sub(a1, a2, dst) + | SubMov(a1, a2, dst) + | New(a1, a2, dst) => { + self.eval_arg(a1); + self.eval_arg(a2); + self.set(dst.name.clone(), Kal::Unknown); + } + + Apply(a, this, a3, dst) | SubCall(this, a, a3, dst) | ThisSubCall(this, a, a3, dst) => { + // TODO: Consider ordering here (only .take consideration (?)) + self.eval_arg(a); + self.eval_arg(a3); + + self.set(this.name.clone(), Kal::Unknown); + self.set(dst.name.clone(), Kal::Unknown); + } + + ConstSubCall(a1, a2, a3, dst) => { + self.eval_arg(a1); + self.eval_arg(a2); + self.eval_arg(a3); + + self.set(dst.name.clone(), Kal::Unknown); + } + + JmpIf(a1, _) => { + self.eval_arg(a1); + } + + Jmp(_) => {} + SetCatch(_, _) => {} + UnsetCatch => {} + RequireMutableThis => { + self.mutable_this_established = true; + } + Next(iter, dst) => { + self.set(iter.name.clone(), Kal::Unknown); + self.set(dst.name.clone(), Kal::Unknown); + } + UnpackIterRes(iter_res, value_reg, done) => { + self.set(iter_res.name.clone(), Kal::Void); + self.set(value_reg.name.clone(), Kal::Unknown); + self.set(done.name.clone(), Kal::Unknown); + } + } + } + + fn eval_arg(&mut self, arg: &mut Value) -> Kal { + match arg { + Value::Void + | Value::Undefined + | Value::Null + | Value::Bool(_) + | Value::Number(_) + | Value::BigInt(_) + | Value::String(_) + | Value::Pointer(_) + | Value::Builtin(_) => Kal::from_value(arg), + Value::Array(array) => { + let mut values = Vec::::new(); + + for item in &mut array.values { + values.push(self.eval_arg(item)); + } + + Kal::Array(Box::new(Array { values })) + } + Value::Object(object) => { + let mut properties = Vec::<(Kal, Kal)>::new(); + + for (k, v) in &mut object.properties { + let k = self.eval_arg(k); + let v = self.eval_arg(v); + + properties.push((k, v)); + } + + Kal::Object(Box::new(Object { properties })) + } + Value::Register(reg) => { + let kal = self.get(reg.name.clone()).clone(); + + let is_take = reg.take; + + if is_take { + self.set(reg.name.clone(), Kal::Void); + } + + match kal.to_value() { + Some(v) => { + // Note: if `reg.take` was true, then we're removing that take operation from the + // register here. This should be ok because well-formed programs should never read from + // a taken register, but we might need to revise this in the future. It definitely means + // it's possible for the optimizer to break hand-written assembly. + *arg = v; + + kal + } + None => match is_take { + true => Kal::Unknown, + false => Kal::Register(reg.clone()), + }, + } + } + } + } + + fn apply_unary_op(&mut self, arg: &mut Value, dst: &Register, op: fn(input: &Val) -> Val) { + match self.apply_unary_op_impl(arg, dst, op) { + Some(_) => {} + None => { + self.set(dst.name.clone(), Kal::Unknown); + } + } + } + + fn apply_unary_op_impl( + &mut self, + arg: &mut Value, + dst: &Register, + op: fn(input: &Val) -> Val, + ) -> Option<()> { + let arg = self.eval_arg(arg).try_to_val()?; + let kal = op(&arg).try_to_kal()?; + + self.set(dst.name.clone(), kal); + + Some(()) + } + + fn apply_binary_op( + &mut self, + left: &mut Value, + right: &mut Value, + dst: &Register, + op: fn(left: &Val, right: &Val) -> Result, + ) { + match self.apply_binary_op_impl(left, right, dst, op) { + Some(_) => {} + None => { + self.set(dst.name.clone(), Kal::Unknown); + } + } + } + + fn apply_binary_op_impl( + &mut self, + left: &mut Value, + right: &mut Value, + dst: &Register, + op: fn(left: &Val, right: &Val) -> Result, + ) -> Option<()> { + let left = self.eval_arg(left).try_to_val()?; + let right = self.eval_arg(right).try_to_val()?; + let kal = op(&left, &right).ok()?.try_to_kal()?; + + self.set(dst.name.clone(), kal); + + Some(()) + } +} diff --git a/valuescript_compiler/src/optimization/mod.rs b/valuescript_compiler/src/optimization/mod.rs index 1edf5c9..abd5266 100644 --- a/valuescript_compiler/src/optimization/mod.rs +++ b/valuescript_compiler/src/optimization/mod.rs @@ -1,11 +1,12 @@ mod collapse_pointers_of_pointers; mod extract_constants; +mod kal; mod optimize; mod remove_meta_lines; mod remove_noops; mod shake_tree; mod simplify; +pub mod try_to_kal; pub mod try_to_val; -pub mod try_to_value; pub use optimize::optimize; diff --git a/valuescript_compiler/src/optimization/simplify.rs b/valuescript_compiler/src/optimization/simplify.rs index 281d0db..e0026f8 100644 --- a/valuescript_compiler/src/optimization/simplify.rs +++ b/valuescript_compiler/src/optimization/simplify.rs @@ -1,15 +1,8 @@ -use std::collections::{HashMap, HashSet}; +use std::mem::take; -use num_bigint::BigInt; -use valuescript_vm::{operations, vs_value::Val}; +use crate::asm::{DefinitionContent, FnLine, Function, Instruction, Module, Register}; -use crate::{ - asm::{DefinitionContent, FnLine, Function, Instruction, Module, Number, Register, Value}, - instruction::InstructionFieldMut, - TryToVal, -}; - -use super::try_to_value::TryToValue; +use super::kal::FnState; pub fn simplify(module: &mut Module) { for defn in &mut module.definitions { @@ -22,367 +15,128 @@ pub fn simplify(module: &mut Module) { } } -#[derive(Default)] -struct FnState { - mutable_this_established: bool, - registers: HashMap, +fn handle_mutation_releases(body: &mut Vec, i: usize) { + let mut calls = Vec::<(Register, usize)>::new(); + + match &mut body[i] { + FnLine::Instruction(instr) => { + let mut skips_needed = 0; + + instr.visit_registers_mut_rev(&mut |rvm| { + skips_needed += 1; + + if rvm.write && !rvm.read { + calls.push((rvm.register.clone(), skips_needed)); + } + }); + } + FnLine::Release(_) | FnLine::Label(_) | FnLine::Empty | FnLine::Comment(_) => {} + }; + + for (released_reg, skips) in calls { + handle_release(body, i, released_reg.clone(), skips); + } } -impl FnState { - fn clear(&mut self) { - *self = Self::default(); - } +fn handle_release( + body: &mut Vec, + i: usize, + released_reg: Register, + skips_needed: usize, +) -> bool { + let mut j = i + 1; + let mut skips = 0; + let mut taken = false; + while j > 0 { + j -= 1; - fn simplify_line(&self, line: &mut FnLine) { - match line { - FnLine::Instruction(instr) => { - if let Instruction::RequireMutableThis = instr { - if self.mutable_this_established { - *line = FnLine::Comment(line.to_string()); - } - } else { - instr.visit_fields_mut(&mut |field| match field { - InstructionFieldMut::Value(arg) => { - self.simplify_arg(arg); - } - _ => {} - }); - } - } - FnLine::Label(..) | FnLine::Empty | FnLine::Comment(..) | FnLine::Release(..) => {} + let instr = match &mut body[j] { + FnLine::Instruction(instr) => instr, + FnLine::Label(_) => return false, + _ => continue, + }; + + if is_jmp_instr(instr) { + return false; } - } - fn simplify_arg(&self, arg: &mut Value) { - arg.visit_values_mut(&mut |value| { - if let Value::Register(reg) = value { - if let Some(new_value) = self.registers.get(®.name) { - *value = new_value.clone(); - } - } - }); - } + let mut write_found = false; - fn apply_line(&mut self, line: &FnLine) { - match line { - FnLine::Instruction(instr) => match instr { - Instruction::End => {} - Instruction::Mov(a1, dst) => { - self.set_register(dst, Some(a1.clone())); + if !taken { + instr.visit_registers_mut_rev(&mut |rvm| { + if skips < skips_needed { + skips += 1; + return; } - Instruction::OpInc(reg) => { - // TODO: Use apply_binary_op? - - let new_value = match self.registers.get(®.name) { - Some(Value::Number(Number(x))) => Some(Value::Number(Number(x + 1.0))), - Some(Value::BigInt(x)) => Some(Value::BigInt(x + BigInt::from(1))), - Some(_) | None => None, - }; - - self.set_register(reg, new_value); - } - Instruction::OpDec(reg) => { - let new_value = match self.registers.get(®.name) { - Some(Value::Number(Number(x))) => Some(Value::Number(Number(x - 1.0))), - Some(Value::BigInt(x)) => Some(Value::BigInt(x - BigInt::from(1))), - Some(_) | None => None, - }; - - self.set_register(reg, new_value); + if rvm.register.name != released_reg.name { + return; } - Instruction::OpNot(a1, dst) => self.apply_unary_op(a1, dst, operations::op_not), - Instruction::OpBitNot(a1, dst) => self.apply_unary_op(a1, dst, operations::op_bit_not), - Instruction::TypeOf(a1, dst) => self.apply_unary_op(a1, dst, operations::op_typeof), - Instruction::UnaryPlus(a1, dst) => self.apply_unary_op(a1, dst, operations::op_unary_plus), - Instruction::UnaryMinus(a1, dst) => { - self.apply_unary_op(a1, dst, operations::op_unary_minus) - } - Instruction::Import(_a1, dst) - | Instruction::ImportStar(_a1, dst) - | Instruction::Cat(_a1, dst) => { - // TODO: cat - self.set_register(dst, None); + if !taken && !rvm.write { + *rvm.register = rvm.register.take(); + taken = true; } - Instruction::Yield(_a1, dst) | Instruction::YieldStar(_a1, dst) => { - self.set_register(dst, None) - } + if !write_found && rvm.write { + write_found = true; - Instruction::Throw(_a1) => {} - - Instruction::OpPlus(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_plus), - Instruction::OpMinus(a1, a2, dst) => { - self.apply_binary_op(a1, a2, dst, operations::op_minus) - } - Instruction::OpMul(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_mul), - Instruction::OpDiv(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_div), - Instruction::OpMod(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_mod), - Instruction::OpExp(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_exp), - Instruction::OpEq(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_eq), - Instruction::OpNe(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_ne), - Instruction::OpTripleEq(a1, a2, dst) => { - self.apply_binary_op(a1, a2, dst, operations::op_triple_eq) - } - Instruction::OpTripleNe(a1, a2, dst) => { - self.apply_binary_op(a1, a2, dst, operations::op_triple_ne) - } - Instruction::OpAnd(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_and), - Instruction::OpOr(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_or), - Instruction::OpLess(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_less), - Instruction::OpLessEq(a1, a2, dst) => { - self.apply_binary_op(a1, a2, dst, operations::op_less_eq) - } - Instruction::OpGreater(a1, a2, dst) => { - self.apply_binary_op(a1, a2, dst, operations::op_greater) - } - Instruction::OpGreaterEq(a1, a2, dst) => { - self.apply_binary_op(a1, a2, dst, operations::op_greater_eq) - } - Instruction::OpNullishCoalesce(a1, a2, dst) => { - self.apply_binary_op(a1, a2, dst, operations::op_nullish_coalesce) - } - Instruction::OpOptionalChain(_a1, _a2, dst) => { - // self.apply_binary_op(a1, a2, dst, operations::op_optional_chain) - // TODO: op_optional_chain takes mut lhs to optimize, but breaks this pattern - self.set_register(dst, None); - } - Instruction::OpBitAnd(a1, a2, dst) => { - self.apply_binary_op(a1, a2, dst, operations::op_bit_and) - } - Instruction::OpBitOr(a1, a2, dst) => { - self.apply_binary_op(a1, a2, dst, operations::op_bit_or) - } - Instruction::OpBitXor(a1, a2, dst) => { - self.apply_binary_op(a1, a2, dst, operations::op_bit_xor) - } - Instruction::OpLeftShift(a1, a2, dst) => { - self.apply_binary_op(a1, a2, dst, operations::op_left_shift) - } - Instruction::OpRightShift(a1, a2, dst) => { - self.apply_binary_op(a1, a2, dst, operations::op_right_shift) - } - Instruction::OpRightShiftUnsigned(a1, a2, dst) => { - self.apply_binary_op(a1, a2, dst, operations::op_right_shift_unsigned) - } - Instruction::InstanceOf(a1, a2, dst) => { - self.apply_binary_op(a1, a2, dst, operations::op_instance_of) - } - Instruction::In(a1, a2, dst) => self.apply_binary_op(a1, a2, dst, operations::op_in), - - Instruction::Call(_a1, _a2, dst) - | Instruction::Bind(_a1, _a2, dst) - | Instruction::Sub(_a1, _a2, dst) - | Instruction::SubMov(_a1, _a2, dst) - | Instruction::New(_a1, _a2, dst) => { - self.set_register(dst, None); - } - - Instruction::Apply(_a, this, _a3, dst) - | Instruction::SubCall(this, _a, _a3, dst) - | Instruction::ThisSubCall(this, _a, _a3, dst) => { - self.set_register(this, None); - self.set_register(dst, None); - } - - Instruction::ConstSubCall(_a1, _a2, _a3, dst) => self.set_register(dst, None), - - Instruction::JmpIf(_a1, _) => {} - - Instruction::Jmp(_) => {} - Instruction::SetCatch(_, _) => {} - Instruction::UnsetCatch => {} - Instruction::RequireMutableThis => { - self.mutable_this_established = true; - } - Instruction::Next(iter, dst) => { - self.set_register(iter, None); - self.set_register(dst, None); - } - Instruction::UnpackIterRes(iter_res, value_reg, done) => { - self.set_register(iter_res, None); - self.set_register(value_reg, None); - self.set_register(done, None); - } - }, - FnLine::Label(..) => self.clear(), - FnLine::Empty | FnLine::Comment(..) => {} - FnLine::Release(reg) => { - self.set_register(reg, None); - } - } - } - - fn set_register(&mut self, reg: &Register, value: Option) { - let mut registers_to_clear = HashSet::::new(); - - for (k, v) in &mut self.registers { - v.visit_values_mut(&mut |value| { - if let Value::Register(reg_value) = value { - if reg_value.name == reg.name { - registers_to_clear.insert(k.clone()); + if !rvm.read && !taken { + *rvm.register = Register::ignore(); } } }); } - for reg_to_clear in registers_to_clear { - self.registers.remove(®_to_clear); - } - - match value { - Some(value) => self.registers.insert(reg.name.clone(), value), - None => self.registers.remove(®.name), - }; - } - - fn apply_unary_op(&mut self, arg: &Value, dst: &Register, op: fn(input: &Val) -> Val) { - match self.apply_unary_op_impl(arg, dst, op) { - Ok(_) => {} - Err(_) => { - self.set_register(dst, None); - } + if write_found { + break; } } - fn apply_unary_op_impl( - &mut self, - arg: &Value, - dst: &Register, - op: fn(input: &Val) -> Val, - ) -> Result<(), Val> { - let arg = arg.clone().try_to_val()?; - let value = op(&arg).try_to_value()?; + taken +} - self.set_register(dst, Some(value)); +fn simplify_fn(mut state: FnState, fn_: &mut Function) { + let mut pending_releases = Vec::::new(); - Ok(()) - } - - fn apply_binary_op( - &mut self, - left: &Value, - right: &Value, - dst: &Register, - op: fn(left: &Val, right: &Val) -> Result, - ) { - match self.apply_binary_op_impl(left, right, dst, op) { - Ok(_) => {} - Err(_) => { - self.set_register(dst, None); + for i in 0..fn_.body.len() { + if let FnLine::Instruction(Instruction::RequireMutableThis) = &fn_.body[i] { + if state.mutable_this_established { + fn_.body[i] = FnLine::Comment(fn_.body[i].to_string()); + continue; } } - } - fn apply_binary_op_impl( - &mut self, - left: &Value, - right: &Value, - dst: &Register, - op: fn(left: &Val, right: &Val) -> Result, - ) -> Result<(), Val> { - let left = left.clone().try_to_val()?; - let right = right.clone().try_to_val()?; - let value = op(&left, &right)?.try_to_value()?; - - self.set_register(dst, Some(value)); - - Ok(()) - } - - fn handle_releases(&self, body: &mut Vec, i: usize) { - let mut calls = Vec::<(Register, usize)>::new(); - - match &mut body[i] { - FnLine::Instruction(instr) => { - let mut skips_needed = 0; - - instr.visit_registers_mut_rev(&mut |rvm| { - skips_needed += 1; - - if rvm.write && !rvm.read { - calls.push((rvm.register.clone(), skips_needed)); - } - }); + if is_jmp_or_label(&fn_.body[i]) && i > 0 { + for released_reg in take(&mut pending_releases) { + handle_release(&mut fn_.body, i - 1, released_reg, 0); } - FnLine::Release(released_reg) => { - calls.push((released_reg.clone(), 0)); - } - FnLine::Label(_) | FnLine::Empty | FnLine::Comment(_) => {} - }; - - for (released_reg, skips) in calls { - self.handle_releases_impl(body, i, released_reg, skips); } + + match &mut fn_.body[i] { + FnLine::Instruction(instr) => state.eval_instruction(instr), + FnLine::Label(_) => state = FnState::default(), + FnLine::Empty | FnLine::Comment(_) => {} + FnLine::Release(reg) => pending_releases.push(reg.clone()), + } + + handle_mutation_releases(&mut fn_.body, i); } - fn handle_releases_impl( - &self, - body: &mut Vec, - i: usize, - released_reg: Register, - skips_needed: usize, - ) { - let mut j = i + 1; - let mut skips = 0; - let mut taken = false; - while j > 0 { - j -= 1; + if !fn_.body.is_empty() { + let last_i = fn_.body.len() - 1; - let instr = match &mut body[j] { - FnLine::Instruction(instr) => instr, - FnLine::Label(_) => return, - _ => continue, - }; - - if is_jmp_instr(instr) { - return; - } - - let mut write_found = false; - - if !taken { - instr.visit_registers_mut_rev(&mut |rvm| { - if skips < skips_needed { - skips += 1; - return; - } - - if rvm.register.name != released_reg.name { - return; - } - - if !taken && !rvm.write { - *rvm.register = rvm.register.take(); - taken = true; - } - - if !write_found && rvm.write { - write_found = true; - - if !rvm.read && !taken { - *rvm.register = Register::ignore(); - } - } - }); - } - - if write_found { - break; - } + for released_reg in pending_releases { + handle_release(&mut fn_.body, last_i, released_reg, 0); } } } -fn simplify_fn(mut state: FnState, fn_: &mut Function) { - for i in 0..fn_.body.len() { - let line = &mut fn_.body[i]; - - state.simplify_line(line); - state.apply_line(line); - - state.handle_releases(&mut fn_.body, i); +fn is_jmp_or_label(line: &FnLine) -> bool { + match line { + FnLine::Instruction(instr) => is_jmp_instr(instr), + FnLine::Label(_) => true, + FnLine::Empty | FnLine::Comment(_) | FnLine::Release(_) => false, } } diff --git a/valuescript_compiler/src/optimization/try_to_kal.rs b/valuescript_compiler/src/optimization/try_to_kal.rs new file mode 100644 index 0000000..434279f --- /dev/null +++ b/valuescript_compiler/src/optimization/try_to_kal.rs @@ -0,0 +1,67 @@ +use valuescript_vm::{vs_value::Val, VsSymbol}; + +use crate::asm::{Builtin, Number}; + +use super::kal::{Array, Kal, Object}; + +pub trait TryToKal { + fn try_to_kal(&self) -> Option; +} + +impl TryToKal for Val { + fn try_to_kal(&self) -> Option { + Some(match self { + Val::Void => Kal::Undefined, + Val::Undefined => Kal::Undefined, + Val::Null => Kal::Null, + Val::Bool(b) => Kal::Bool(*b), + Val::Number(n) => Kal::Number(Number(*n)), + Val::BigInt(n) => Kal::BigInt(n.clone()), + Val::Symbol(sym) => match sym { + VsSymbol::ITERATOR => Kal::Builtin(Builtin { + name: "SymbolIterator".to_string(), + }), + }, + Val::String(s) => Kal::String(s.to_string()), + Val::Array(arr) => { + let mut values = Vec::::new(); + + for value in &arr.elements { + values.push(value.try_to_kal()?); + } + + Kal::Array(Box::new(Array { values })) + } + Val::Object(obj) => { + if obj.prototype.is_some() { + // TODO: convert object with prototype to Kal + return None; + } + + let mut properties = Vec::<(Kal, Kal)>::new(); + + for (k, v) in &obj.symbol_map { + let k = match k { + VsSymbol::ITERATOR => Kal::Builtin(Builtin { + name: "SymbolIterator".to_string(), + }), + }; + + properties.push((k, v.try_to_kal()?)); + } + + for (k, v) in &obj.string_map { + properties.push((Kal::String(k.clone()), v.try_to_kal()?)); + } + + Kal::Object(Box::new(Object { properties })) + } + // TODO: support more of these + Val::Function(..) + | Val::Class(..) + | Val::Static(..) + | Val::Dynamic(..) + | Val::CopyCounter(..) => return None, + }) + } +} diff --git a/valuescript_compiler/src/optimization/try_to_value.rs b/valuescript_compiler/src/optimization/try_to_value.rs deleted file mode 100644 index 81a0733..0000000 --- a/valuescript_compiler/src/optimization/try_to_value.rs +++ /dev/null @@ -1,66 +0,0 @@ -use valuescript_vm::{ - vs_value::{ToVal, Val}, - VsSymbol, -}; - -use crate::asm::{Array, Builtin, Number, Object, Value}; - -pub trait TryToValue { - fn try_to_value(&self) -> Result; -} - -impl TryToValue for Val { - fn try_to_value(&self) -> Result { - Ok(match self { - Val::Void => Value::Undefined, - Val::Undefined => Value::Undefined, - Val::Null => Value::Null, - Val::Bool(b) => Value::Bool(*b), - Val::Number(n) => Value::Number(Number(*n)), - Val::BigInt(n) => Value::BigInt(n.clone()), - Val::Symbol(sym) => match sym { - VsSymbol::ITERATOR => Value::Builtin(Builtin { - name: "SymbolIterator".to_string(), - }), - }, - Val::String(s) => Value::String(s.to_string()), - Val::Array(arr) => { - let mut values = Vec::::new(); - - for value in &arr.elements { - values.push(value.try_to_value()?); - } - - Value::Array(Box::new(Array { values })) - } - Val::Object(obj) => { - if obj.prototype.is_some() { - return Err("can't (yet?) convert object with prototype to Value".to_val()); - } - - let mut properties = Vec::<(Value, Value)>::new(); - - for (k, v) in &obj.symbol_map { - let k = match k { - VsSymbol::ITERATOR => Value::Builtin(Builtin { - name: "SymbolIterator".to_string(), - }), - }; - - properties.push((k, v.try_to_value()?)); - } - - for (k, v) in &obj.string_map { - properties.push((Value::String(k.clone()), v.try_to_value()?)); - } - - Value::Object(Box::new(Object { properties })) - } - Val::Function(..) - | Val::Class(..) - | Val::Static(..) - | Val::Dynamic(..) - | Val::CopyCounter(..) => return Err("TODO: support more of these".to_val()), - }) - } -}