From 7e6bef92b588612cf9009056dd53806152a3fc45 Mon Sep 17 00:00:00 2001 From: raskad <32105367+raskad@users.noreply.github.com> Date: Thu, 21 Oct 2021 22:27:14 +0200 Subject: [PATCH] Implement object literals for vm (#1668) --- boa/src/bytecompiler.rs | 125 ++++++++++++++++++++++++++++++++------- boa/src/vm/code_block.rs | 4 ++ boa/src/vm/mod.rs | 105 +++++++++++++++++++++++++++++--- boa/src/vm/opcode.rs | 40 +++++++++++++ 4 files changed, 246 insertions(+), 28 deletions(-) diff --git a/boa/src/bytecompiler.rs b/boa/src/bytecompiler.rs index bb89828942..1c3bc8b9ca 100644 --- a/boa/src/bytecompiler.rs +++ b/boa/src/bytecompiler.rs @@ -3,7 +3,10 @@ use gc::Gc; use crate::{ builtins::function::ThisMode, syntax::ast::{ - node::{Declaration, GetConstField, GetField, StatementList}, + node::{ + Declaration, GetConstField, GetField, MethodDefinitionKind, PropertyDefinition, + PropertyName, StatementList, + }, op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}, Const, Node, }, @@ -67,19 +70,19 @@ impl ByteCompiler { } #[inline] - fn get_or_insert_literal(&mut self, liternal: Literal) -> u32 { - if let Some(index) = self.literals_map.get(&liternal) { + fn get_or_insert_literal(&mut self, literal: Literal) -> u32 { + if let Some(index) = self.literals_map.get(&literal) { return *index; } - let value = match liternal.clone() { + let value = match literal.clone() { Literal::String(value) => JsValue::new(value), Literal::BigInt(value) => JsValue::new(value), }; let index = self.code_block.literals.len() as u32; self.code_block.literals.push(value); - self.literals_map.insert(liternal, index); + self.literals_map.insert(literal, index); index } @@ -153,8 +156,8 @@ impl ByteCompiler { } #[inline] - fn emit_push_literal(&mut self, liternal: Literal) { - let index = self.get_or_insert_literal(liternal); + fn emit_push_literal(&mut self, literal: Literal) { + let index = self.get_or_insert_literal(literal); self.emit(Opcode::PushLiteral, &[index]); } @@ -583,10 +586,97 @@ impl ByteCompiler { } } Node::Object(object) => { - if object.properties().is_empty() { - self.emit(Opcode::PushEmptyObject, &[]); - } else { - todo!("object literal with properties"); + self.emit_opcode(Opcode::PushEmptyObject); + for property in object.properties() { + self.emit_opcode(Opcode::Dup); + match property { + PropertyDefinition::IdentifierReference(identifier_reference) => { + let index = self.get_or_insert_name(identifier_reference); + self.emit(Opcode::SetPropertyByName, &[index]); + } + PropertyDefinition::Property(name, node) => { + self.compile_stmt(node, true); + self.emit_opcode(Opcode::Swap); + match name { + PropertyName::Literal(name) => { + let index = self.get_or_insert_name(name); + self.emit(Opcode::SetPropertyByName, &[index]); + } + PropertyName::Computed(name_node) => { + self.compile_stmt(name_node, true); + self.emit_opcode(Opcode::Swap); + self.emit_opcode(Opcode::SetPropertyByValue); + } + } + } + PropertyDefinition::MethodDefinition(kind, name, func) => { + match kind { + MethodDefinitionKind::Get => { + self.compile_stmt(&func.clone().into(), true); + self.emit_opcode(Opcode::Swap); + match name { + PropertyName::Literal(name) => { + let index = self.get_or_insert_name(name); + self.emit(Opcode::SetPropertyGetterByName, &[index]); + } + PropertyName::Computed(name_node) => { + self.compile_stmt(name_node, true); + self.emit_opcode(Opcode::Swap); + self.emit_opcode(Opcode::SetPropertyGetterByValue); + } + } + } + MethodDefinitionKind::Set => { + self.compile_stmt(&func.clone().into(), true); + self.emit_opcode(Opcode::Swap); + match name { + PropertyName::Literal(name) => { + let index = self.get_or_insert_name(name); + self.emit(Opcode::SetPropertySetterByName, &[index]); + } + PropertyName::Computed(name_node) => { + self.compile_stmt(name_node, true); + self.emit_opcode(Opcode::Swap); + self.emit_opcode(Opcode::SetPropertySetterByValue); + } + } + } + MethodDefinitionKind::Ordinary => { + self.compile_stmt(&func.clone().into(), true); + self.emit_opcode(Opcode::Swap); + match name { + PropertyName::Literal(name) => { + let index = self.get_or_insert_name(name); + self.emit(Opcode::SetPropertyByName, &[index]); + } + PropertyName::Computed(name_node) => { + self.compile_stmt(name_node, true); + self.emit_opcode(Opcode::Swap); + self.emit_opcode(Opcode::SetPropertyByValue); + } + } + } + MethodDefinitionKind::Generator => { + // TODO: Implement generators + self.emit_opcode(Opcode::PushUndefined); + self.emit_opcode(Opcode::Swap); + match name { + PropertyName::Literal(name) => { + let index = self.get_or_insert_name(name); + self.emit(Opcode::SetPropertyByName, &[index]); + } + PropertyName::Computed(name_node) => { + self.compile_stmt(name_node, true); + self.emit_opcode(Opcode::Swap); + self.emit_opcode(Opcode::SetPropertyByValue); + } + } + } + } + } + // TODO: Spread Object + PropertyDefinition::SpreadObject(_) => todo!(), + } } if !use_expr { @@ -883,7 +973,7 @@ impl ByteCompiler { Arrow, } - let (kind, name, paramaters, body) = match function { + let (kind, name, parameters, body) = match function { Node::FunctionDecl(function) => ( FunctionKind::Declaration, Some(function.name()), @@ -905,7 +995,7 @@ impl ByteCompiler { _ => unreachable!(), }; - let length = paramaters.len() as u32; + let length = parameters.len() as u32; let mut code = CodeBlock::new(name.unwrap_or("").into(), length, false, true); if let FunctionKind::Arrow = kind { @@ -926,7 +1016,7 @@ impl ByteCompiler { compiler.compile_stmt(node, false); } - compiler.code_block.params = paramaters.to_owned().into_boxed_slice(); + compiler.code_block.params = parameters.to_owned().into_boxed_slice(); // TODO These are redundant if a function returns so may need to check if a function returns and adding these if it doesn't compiler.emit(Opcode::PushUndefined, &[]); @@ -945,12 +1035,7 @@ impl ByteCompiler { let access = Access::Variable { index }; self.access_set(access, None, false); } - FunctionKind::Expression => { - if use_expr { - self.emit(Opcode::Dup, &[]); - } - } - FunctionKind::Arrow => { + FunctionKind::Expression | FunctionKind::Arrow => { if !use_expr { self.emit(Opcode::Pop, &[]); } diff --git a/boa/src/vm/code_block.rs b/boa/src/vm/code_block.rs index 9216fcf3df..6b482a0983 100644 --- a/boa/src/vm/code_block.rs +++ b/boa/src/vm/code_block.rs @@ -160,6 +160,8 @@ impl CodeBlock { | Opcode::SetName | Opcode::GetPropertyByName | Opcode::SetPropertyByName + | Opcode::SetPropertyGetterByName + | Opcode::SetPropertySetterByName | Opcode::DeletePropertyByName => { let operand = self.read::(*pc); *pc += size_of::(); @@ -210,6 +212,8 @@ impl CodeBlock { | Opcode::Dec | Opcode::GetPropertyByValue | Opcode::SetPropertyByValue + | Opcode::SetPropertyGetterByValue + | Opcode::SetPropertySetterByValue | Opcode::DeletePropertyByValue | Opcode::ToBoolean | Opcode::Throw diff --git a/boa/src/vm/mod.rs b/boa/src/vm/mod.rs index 4b49f97137..13fe231218 100644 --- a/boa/src/vm/mod.rs +++ b/boa/src/vm/mod.rs @@ -3,23 +3,19 @@ //! plus an interpreter to execute those instructions use crate::{ - builtins::Array, environment::lexical_environment::VariableScope, BoaProfiler, Context, - JsResult, JsValue, + builtins::Array, environment::lexical_environment::VariableScope, property::PropertyDescriptor, + vm::code_block::Readable, BoaProfiler, Context, JsResult, JsValue, }; +use std::{convert::TryInto, mem::size_of, time::Instant}; mod call_frame; mod code_block; mod opcode; pub use call_frame::CallFrame; -pub use code_block::CodeBlock; -pub use code_block::JsVmFunction; +pub use code_block::{CodeBlock, JsVmFunction}; pub use opcode::Opcode; -use std::{convert::TryInto, mem::size_of, time::Instant}; - -use self::code_block::Readable; - #[cfg(test)] mod tests; /// Virtual Machine. @@ -405,6 +401,99 @@ impl Context { let key = key.to_property_key(self)?; object.set(key, value, true, self)?; } + Opcode::SetPropertyGetterByName => { + let index = self.vm.read::(); + let object = self.vm.pop(); + let value = self.vm.pop(); + let object = object.to_object(self)?; + + let name = self.vm.frame().code.variables[index as usize] + .clone() + .into(); + let set = object + .__get_own_property__(&name, self)? + .as_ref() + .and_then(|a| a.set()) + .cloned(); + object.__define_own_property__( + name, + PropertyDescriptor::builder() + .maybe_get(Some(value)) + .maybe_set(set) + .enumerable(true) + .configurable(true) + .build(), + self, + )?; + } + Opcode::SetPropertyGetterByValue => { + let object = self.vm.pop(); + let key = self.vm.pop(); + let value = self.vm.pop(); + let object = object.to_object(self)?; + let name = key.to_property_key(self)?; + let set = object + .__get_own_property__(&name, self)? + .as_ref() + .and_then(|a| a.set()) + .cloned(); + object.__define_own_property__( + name, + PropertyDescriptor::builder() + .maybe_get(Some(value)) + .maybe_set(set) + .enumerable(true) + .configurable(true) + .build(), + self, + )?; + } + Opcode::SetPropertySetterByName => { + let index = self.vm.read::(); + let object = self.vm.pop(); + let value = self.vm.pop(); + let object = object.to_object(self)?; + let name = self.vm.frame().code.variables[index as usize] + .clone() + .into(); + let get = object + .__get_own_property__(&name, self)? + .as_ref() + .and_then(|a| a.get()) + .cloned(); + object.__define_own_property__( + name, + PropertyDescriptor::builder() + .maybe_set(Some(value)) + .maybe_get(get) + .enumerable(true) + .configurable(true) + .build(), + self, + )?; + } + Opcode::SetPropertySetterByValue => { + let object = self.vm.pop(); + let key = self.vm.pop(); + let value = self.vm.pop(); + let object = object.to_object(self)?; + let name = key.to_property_key(self)?; + let get = object + .__get_own_property__(&name, self)? + .as_ref() + .and_then(|a| a.get()) + .cloned(); + object.__define_own_property__( + name, + PropertyDescriptor::builder() + .maybe_set(Some(value)) + .maybe_get(get) + .enumerable(true) + .configurable(true) + .build(), + self, + )?; + } Opcode::DeletePropertyByName => { let index = self.vm.read::(); let key = self.vm.frame().code.variables[index as usize].clone(); diff --git a/boa/src/vm/opcode.rs b/boa/src/vm/opcode.rs index ae2fc43718..b7e34889d9 100644 --- a/boa/src/vm/opcode.rs +++ b/boa/src/vm/opcode.rs @@ -456,6 +456,42 @@ pub enum Opcode { /// Stack: value, key, object **=>** SetPropertyByValue, + /// Sets a getter property by name of an object. + /// + /// Like `get name() value` + /// + /// Operands: name_index: `u32` + /// + /// Stack: value, object **=>** + SetPropertyGetterByName, + + /// Sets a getter property by value of an object. + /// + /// Like `get [key]() value` + /// + /// Operands: + /// + /// Stack: value, key, object **=>** + SetPropertyGetterByValue, + + /// Sets a setter property by name of an object. + /// + /// Like `set name() value` + /// + /// Operands: name_index: `u32` + /// + /// Stack: value, object **=>** + SetPropertySetterByName, + + /// Sets a setter property by value of an object. + /// + /// Like `set [key]() value` + /// + /// Operands: + /// + /// Stack: value, key, object **=>** + SetPropertySetterByValue, + /// Deletes a property by name of an object. /// /// Like `delete object.key.` @@ -639,6 +675,10 @@ impl Opcode { Opcode::GetPropertyByValue => "GetPropertyByValue", Opcode::SetPropertyByName => "SetPropertyByName", Opcode::SetPropertyByValue => "SetPropertyByValue", + Opcode::SetPropertyGetterByName => "SetPropertyGetterByName", + Opcode::SetPropertyGetterByValue => "SetPropertyGetterByValue", + Opcode::SetPropertySetterByName => "SetPropertySetterByName", + Opcode::SetPropertySetterByValue => "SetPropertySetterByValue", Opcode::DeletePropertyByName => "DeletePropertyByName", Opcode::DeletePropertyByValue => "DeletePropertyByValue", Opcode::Jump => "Jump",