diff --git a/core/ast/src/scope.rs b/core/ast/src/scope.rs index f453299712a..5e31d8d29eb 100644 --- a/core/ast/src/scope.rs +++ b/core/ast/src/scope.rs @@ -454,6 +454,12 @@ impl IdentifierReference { self.locator.scope > 0 && !self.escapes } + /// Returns if the binding is on the global object. + #[must_use] + pub fn is_global_object(&self) -> bool { + self.locator.scope == 0 + } + /// Check if this identifier reference is lexical. #[must_use] pub fn is_lexical(&self) -> bool { diff --git a/core/engine/src/bytecompiler/mod.rs b/core/engine/src/bytecompiler/mod.rs index 657261f3e80..b4ec960cc4c 100644 --- a/core/engine/src/bytecompiler/mod.rs +++ b/core/engine/src/bytecompiler/mod.rs @@ -451,6 +451,7 @@ pub struct ByteCompiler<'ctx> { pub(crate) enum BindingKind { Stack(u32), Local(u32), + Global(u32), } impl<'ctx> ByteCompiler<'ctx> { @@ -601,6 +602,17 @@ impl<'ctx> ByteCompiler<'ctx> { #[inline] pub(crate) fn get_or_insert_binding(&mut self, binding: IdentifierReference) -> BindingKind { + if binding.is_global_object() { + if let Some(index) = self.bindings_map.get(&binding.locator()) { + return BindingKind::Global(*index); + } + + let index = self.bindings.len() as u32; + self.bindings.push(binding.locator().clone()); + self.bindings_map.insert(binding.locator(), index); + return BindingKind::Global(index); + } + if binding.local() { return BindingKind::Local( *self @@ -732,6 +744,19 @@ impl<'ctx> ByteCompiler<'ctx> { pub(crate) fn emit_binding_access(&mut self, opcode: Opcode, binding: &BindingKind) { match binding { + BindingKind::Global(index) => match opcode { + Opcode::SetNameByLocator => self.emit_opcode(opcode), + Opcode::GetName => { + let ic_index = self.ic.len() as u32; + let name = self.bindings[*index as usize].name().clone(); + self.ic.push(InlineCache::new(name)); + self.emit( + Opcode::GetNameGlobal, + &[Operand::Varying(*index), Operand::Varying(ic_index)], + ); + } + _ => self.emit_with_varying_operand(opcode, *index), + }, BindingKind::Stack(index) => match opcode { Opcode::SetNameByLocator => self.emit_opcode(opcode), _ => self.emit_with_varying_operand(opcode, *index), diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index dd44daf1e8e..273607ad438 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -472,6 +472,7 @@ impl CodeBlock { | Instruction::DefInitVar { index } | Instruction::PutLexicalValue { index } | Instruction::GetName { index } + | Instruction::GetNameGlobal { index, .. } | Instruction::GetLocator { index } | Instruction::GetNameAndLocator { index } | Instruction::GetNameOrUndefined { index } @@ -737,8 +738,7 @@ impl CodeBlock { | Instruction::Reserved45 | Instruction::Reserved46 | Instruction::Reserved47 - | Instruction::Reserved48 - | Instruction::Reserved49 => unreachable!("Reserved opcodes are unrechable"), + | Instruction::Reserved48 => unreachable!("Reserved opcodes are unreachable"), } } } diff --git a/core/engine/src/vm/flowgraph/mod.rs b/core/engine/src/vm/flowgraph/mod.rs index baf607b03a9..8d53ba2718c 100644 --- a/core/engine/src/vm/flowgraph/mod.rs +++ b/core/engine/src/vm/flowgraph/mod.rs @@ -250,6 +250,7 @@ impl CodeBlock { | Instruction::DefInitVar { .. } | Instruction::PutLexicalValue { .. } | Instruction::GetName { .. } + | Instruction::GetNameGlobal { .. } | Instruction::GetLocator { .. } | Instruction::GetNameAndLocator { .. } | Instruction::GetNameOrUndefined { .. } @@ -515,8 +516,7 @@ impl CodeBlock { | Instruction::Reserved45 | Instruction::Reserved46 | Instruction::Reserved47 - | Instruction::Reserved48 - | Instruction::Reserved49 => unreachable!("Reserved opcodes are unrechable"), + | Instruction::Reserved48 => unreachable!("Reserved opcodes are unreachable"), } } diff --git a/core/engine/src/vm/opcode/get/name.rs b/core/engine/src/vm/opcode/get/name.rs index 32671a457b8..076ee645821 100644 --- a/core/engine/src/vm/opcode/get/name.rs +++ b/core/engine/src/vm/opcode/get/name.rs @@ -1,5 +1,7 @@ use crate::{ error::JsNativeError, + object::{internal_methods::InternalMethodContext, shape::slot::SlotAttributes}, + property::PropertyKey, vm::{opcode::Operation, CompletionType}, Context, JsResult, JsValue, }; @@ -31,8 +33,8 @@ impl Operation for GetName { const COST: u8 = 4; fn execute(context: &mut Context) -> JsResult { - let index = context.vm.read::(); - Self::operation(context, index as usize) + let index = context.vm.read::() as usize; + Self::operation(context, index) } fn execute_with_u16_operands(context: &mut Context) -> JsResult { @@ -41,8 +43,106 @@ impl Operation for GetName { } fn execute_with_u32_operands(context: &mut Context) -> JsResult { - let index = context.vm.read::(); - Self::operation(context, index as usize) + let index = context.vm.read::() as usize; + Self::operation(context, index) + } +} + +/// `GetNameGlobal` implements the Opcode Operation for `Opcode::GetNameGlobal` +/// +/// Operation: +/// - Find a binding in the global object and push its value. +#[derive(Debug, Clone, Copy)] +pub(crate) struct GetNameGlobal; + +impl GetNameGlobal { + fn operation(context: &mut Context, index: usize, ic_index: usize) -> JsResult { + let mut binding_locator = context.vm.frame().code_block.bindings[index].clone(); + context.find_runtime_binding(&mut binding_locator)?; + + if binding_locator.is_global() { + let object = context.global_object(); + + let ic = &context.vm.frame().code_block().ic[ic_index]; + + let object_borrowed = object.borrow(); + if let Some((shape, slot)) = ic.match_or_reset(object_borrowed.shape()) { + let mut result = if slot.attributes.contains(SlotAttributes::PROTOTYPE) { + let prototype = shape.prototype().expect("prototype should have value"); + let prototype = prototype.borrow(); + prototype.properties().storage[slot.index as usize].clone() + } else { + object_borrowed.properties().storage[slot.index as usize].clone() + }; + + drop(object_borrowed); + if slot.attributes.has_get() && result.is_object() { + result = result.as_object().expect("should contain getter").call( + &object.clone().into(), + &[], + context, + )?; + } + context.vm.push(result); + return Ok(CompletionType::Normal); + } + + drop(object_borrowed); + + let key: PropertyKey = ic.name.clone().into(); + + let context = &mut InternalMethodContext::new(context); + let Some(result) = object.__try_get__(&key, object.clone().into(), context)? else { + let name = binding_locator.name().to_std_string_escaped(); + return Err(JsNativeError::reference() + .with_message(format!("{name} is not defined")) + .into()); + }; + + // Cache the property. + let slot = *context.slot(); + if slot.is_cachable() { + let ic = &context.vm.frame().code_block.ic[ic_index]; + let object_borrowed = object.borrow(); + let shape = object_borrowed.shape(); + ic.set(shape, slot); + } + + context.vm.push(result); + return Ok(CompletionType::Normal); + } + + let value = context.get_binding(&binding_locator)?.ok_or_else(|| { + let name = binding_locator.name().to_std_string_escaped(); + JsNativeError::reference().with_message(format!("{name} is not defined")) + })?; + + context.vm.push(value); + Ok(CompletionType::Normal) + } +} + +impl Operation for GetNameGlobal { + const NAME: &'static str = "GetNameGlobal"; + const INSTRUCTION: &'static str = "INST - GetNameGlobal"; + const COST: u8 = 4; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::() as usize; + let ic_index = context.vm.read::() as usize; + Self::operation(context, index, ic_index) + } + + fn execute_with_u16_operands(context: &mut Context) -> JsResult { + let index = context.vm.read::() as usize; + let ic_index = context.vm.read::() as usize; + Self::operation(context, index, ic_index) + } + + fn execute_with_u32_operands(context: &mut Context) -> JsResult { + let index = context.vm.read::() as usize; + let ic_index = context.vm.read::() as usize; + Self::operation(context, index, ic_index) } } diff --git a/core/engine/src/vm/opcode/mod.rs b/core/engine/src/vm/opcode/mod.rs index 5d2f075c8a2..f0d49513274 100644 --- a/core/engine/src/vm/opcode/mod.rs +++ b/core/engine/src/vm/opcode/mod.rs @@ -1115,6 +1115,13 @@ generate_opcodes! { /// Stack: **=>** value GetName { index: VaryingOperand }, + /// Find a binding in the global object and push its value. + /// + /// Operands: index: `VaryingOperand`, ic_index: `VaryingOperand`, + /// + /// Stack: **=>** value + GetNameGlobal { index: VaryingOperand, ic_index: VaryingOperand }, + /// Find a binding on the environment and set the `current_binding` of the current frame. /// /// Operands: index: `u32` @@ -2280,8 +2287,6 @@ generate_opcodes! { Reserved47 => Reserved, /// Reserved [`Opcode`]. Reserved48 => Reserved, - /// Reserved [`Opcode`]. - Reserved49 => Reserved, } /// Specific opcodes for bindings.