From c3fe9b4df8c50a181d5bb8cf6027a39dd4d508ec Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sat, 28 Nov 2020 16:02:27 +0100 Subject: [PATCH] LibJS: Add a scope object abstraction Both GlobalObject and LexicalEnvironment now inherit from ScopeObject, and the VM's call frames point to a ScopeObject chain rather than just a LexicalEnvironment chain. This gives us much more flexibility to implement things like "with", and also unifies some of the code paths that previously required special handling of the global object. There's a bunch of more cleanup that can be done in the wake of this change, and there might be some oversights in the handling of the "super" keyword, but this generally seems like a good architectural improvement. :^) --- Libraries/LibJS/AST.cpp | 10 ++-- Libraries/LibJS/CMakeLists.txt | 1 + Libraries/LibJS/Forward.h | 1 + Libraries/LibJS/Interpreter.cpp | 17 ++++-- Libraries/LibJS/Interpreter.h | 3 +- Libraries/LibJS/Runtime/GlobalObject.cpp | 27 ++++++++- Libraries/LibJS/Runtime/GlobalObject.h | 13 +++- .../LibJS/Runtime/LexicalEnvironment.cpp | 26 ++++---- Libraries/LibJS/Runtime/LexicalEnvironment.h | 28 ++++----- Libraries/LibJS/Runtime/Object.h | 2 + Libraries/LibJS/Runtime/ScopeObject.cpp | 49 +++++++++++++++ Libraries/LibJS/Runtime/ScopeObject.h | 60 +++++++++++++++++++ Libraries/LibJS/Runtime/ScriptFunction.cpp | 20 ++++--- Libraries/LibJS/Runtime/ScriptFunction.h | 6 +- Libraries/LibJS/Runtime/VM.cpp | 58 +++++++++--------- Libraries/LibJS/Runtime/VM.h | 12 ++-- 16 files changed, 241 insertions(+), 92 deletions(-) create mode 100644 Libraries/LibJS/Runtime/ScopeObject.cpp create mode 100644 Libraries/LibJS/Runtime/ScopeObject.h diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index e8880cd4020..4b4e646af49 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -101,7 +101,7 @@ Value FunctionDeclaration::execute(Interpreter&, GlobalObject&) const Value FunctionExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const { - return ScriptFunction::create(global_object, name(), body(), parameters(), function_length(), interpreter.current_environment(), is_strict_mode() || interpreter.vm().in_strict_mode(), m_is_arrow_function); + return ScriptFunction::create(global_object, name(), body(), parameters(), function_length(), interpreter.current_scope(), is_strict_mode() || interpreter.vm().in_strict_mode(), m_is_arrow_function); } Value ExpressionStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const @@ -128,7 +128,7 @@ CallExpression::ThisAndCallee CallExpression::compute_this_and_callee(Interprete if (m_callee->is_member_expression()) { auto& member_expression = static_cast(*m_callee); bool is_super_property_lookup = member_expression.object().is_super_expression(); - auto lookup_target = is_super_property_lookup ? vm.current_environment()->get_super_base() : member_expression.object().execute(interpreter, global_object); + auto lookup_target = is_super_property_lookup ? interpreter.current_environment()->get_super_base() : member_expression.object().execute(interpreter, global_object); if (vm.exception()) return {}; if (is_super_property_lookup && lookup_target.is_nullish()) { @@ -205,7 +205,7 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj if (result.is_object()) new_object = &result.as_object(); } else if (m_callee->is_super_expression()) { - auto* super_constructor = vm.current_environment()->current_function()->prototype(); + auto* super_constructor = interpreter.current_environment()->current_function()->prototype(); // FIXME: Functions should track their constructor kind. if (!super_constructor || !super_constructor->is_function()) { vm.throw_exception(global_object, ErrorType::NotAConstructor, "Super constructor"); @@ -215,7 +215,7 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj if (vm.exception()) return {}; - vm.current_environment()->bind_this_value(global_object, result); + interpreter.current_environment()->bind_this_value(global_object, result); } else { result = vm.call(function, this_value, move(arguments)); } @@ -801,7 +801,7 @@ Value ClassDeclaration::execute(Interpreter& interpreter, GlobalObject& global_o if (interpreter.exception()) return {}; - interpreter.current_environment()->set(global_object, m_class_expression->name(), { class_constructor, DeclarationKind::Let }); + interpreter.current_scope()->put_to_scope(m_class_expression->name(), { class_constructor, DeclarationKind::Let }); return js_undefined(); } diff --git a/Libraries/LibJS/CMakeLists.txt b/Libraries/LibJS/CMakeLists.txt index 03323a6b70b..39efe3281d1 100644 --- a/Libraries/LibJS/CMakeLists.txt +++ b/Libraries/LibJS/CMakeLists.txt @@ -61,6 +61,7 @@ set(SOURCES Runtime/RegExpConstructor.cpp Runtime/RegExpObject.cpp Runtime/RegExpPrototype.cpp + Runtime/ScopeObject.cpp Runtime/ScriptFunction.cpp Runtime/Shape.cpp Runtime/StringConstructor.cpp diff --git a/Libraries/LibJS/Forward.h b/Libraries/LibJS/Forward.h index 3ad96d44592..57cf1e33dce 100644 --- a/Libraries/LibJS/Forward.h +++ b/Libraries/LibJS/Forward.h @@ -124,6 +124,7 @@ class NativeProperty; class PrimitiveString; class Reference; class ScopeNode; +class ScopeObject; class Shape; class Statement; class Symbol; diff --git a/Libraries/LibJS/Interpreter.cpp b/Libraries/LibJS/Interpreter.cpp index b6c903643ad..e96ff48c09c 100644 --- a/Libraries/LibJS/Interpreter.cpp +++ b/Libraries/LibJS/Interpreter.cpp @@ -72,8 +72,7 @@ Value Interpreter::run(GlobalObject& global_object, const Program& program) global_call_frame.this_value = &global_object; static FlyString global_execution_context_name = "(global execution context)"; global_call_frame.function_name = global_execution_context_name; - global_call_frame.environment = heap().allocate(global_object, LexicalEnvironment::EnvironmentRecordType::Global); - global_call_frame.environment->bind_this_value(global_object, &global_object); + global_call_frame.scope = &global_object; ASSERT(!vm.exception()); global_call_frame.is_strict_mode = program.is_strict_mode(); vm.push_call_frame(global_call_frame, global_object); @@ -96,7 +95,7 @@ const GlobalObject& Interpreter::global_object() const void Interpreter::enter_scope(const ScopeNode& scope_node, ArgumentVector arguments, ScopeType scope_type, GlobalObject& global_object) { for (auto& declaration : scope_node.functions()) { - auto* function = ScriptFunction::create(global_object, declaration.name(), declaration.body(), declaration.parameters(), declaration.function_length(), current_environment(), declaration.is_strict_mode()); + auto* function = ScriptFunction::create(global_object, declaration.name(), declaration.body(), declaration.parameters(), declaration.function_length(), current_scope(), declaration.is_strict_mode()); vm().set_variable(declaration.name(), function, global_object); } @@ -127,8 +126,8 @@ void Interpreter::enter_scope(const ScopeNode& scope_node, ArgumentVector argume bool pushed_lexical_environment = false; if (!scope_variables_with_declaration_kind.is_empty()) { - auto* block_lexical_environment = heap().allocate(global_object, move(scope_variables_with_declaration_kind), current_environment()); - vm().call_frame().environment = block_lexical_environment; + auto* block_lexical_environment = heap().allocate(global_object, move(scope_variables_with_declaration_kind), current_scope()); + vm().call_frame().scope = block_lexical_environment; pushed_lexical_environment = true; } @@ -140,7 +139,7 @@ void Interpreter::exit_scope(const ScopeNode& scope_node) while (!m_scope_stack.is_empty()) { auto popped_scope = m_scope_stack.take_last(); if (popped_scope.pushed_environment) - vm().call_frame().environment = vm().call_frame().environment->parent(); + vm().call_frame().scope = vm().call_frame().scope->parent(); if (popped_scope.scope_node.ptr() == &scope_node) break; } @@ -185,4 +184,10 @@ Value Interpreter::execute_statement(GlobalObject& global_object, const Statemen return did_return ? vm().last_value() : js_undefined(); } +LexicalEnvironment* Interpreter::current_environment() +{ + ASSERT(vm().call_frame().scope->is_lexical_environment()); + return static_cast(vm().call_frame().scope); +} + } diff --git a/Libraries/LibJS/Interpreter.h b/Libraries/LibJS/Interpreter.h index c76b6ef1107..e9a89d7993a 100644 --- a/Libraries/LibJS/Interpreter.h +++ b/Libraries/LibJS/Interpreter.h @@ -71,7 +71,8 @@ public: Heap& heap() { return vm().heap(); } Exception* exception() { return vm().exception(); } - LexicalEnvironment* current_environment() { return vm().current_environment(); } + ScopeObject* current_scope() { return vm().current_scope(); } + LexicalEnvironment* current_environment(); void enter_scope(const ScopeNode&, ArgumentVector, ScopeType, GlobalObject&); void exit_scope(const ScopeNode&); diff --git a/Libraries/LibJS/Runtime/GlobalObject.cpp b/Libraries/LibJS/Runtime/GlobalObject.cpp index 84e85de8c12..d878996b44e 100644 --- a/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -68,7 +68,7 @@ namespace JS { GlobalObject::GlobalObject() - : Object(GlobalObjectTag::Tag) + : ScopeObject(GlobalObjectTag::Tag) , m_console(make(*this)) { } @@ -149,7 +149,7 @@ GlobalObject::~GlobalObject() void GlobalObject::visit_edges(Visitor& visitor) { - Object::visit_edges(visitor); + Base::visit_edges(visitor); visitor.visit(m_empty_object_shape); visitor.visit(m_new_object_shape); @@ -205,4 +205,27 @@ JS_DEFINE_NATIVE_FUNCTION(GlobalObject::parse_float) return js_nan(); } +Optional GlobalObject::get_from_scope(const FlyString& name) const +{ + auto value = get(name); + if (value.is_empty()) + return {}; + return Variable { value, DeclarationKind::Var }; +} + +void GlobalObject::put_to_scope(const FlyString& name, Variable variable) +{ + put(name, variable.value); +} + +bool GlobalObject::has_this_binding() const +{ + return true; +} + +Value GlobalObject::get_this_binding(GlobalObject&) const +{ + return Value(this); +} + } diff --git a/Libraries/LibJS/Runtime/GlobalObject.h b/Libraries/LibJS/Runtime/GlobalObject.h index 1bf49be795a..b0d8214478f 100644 --- a/Libraries/LibJS/Runtime/GlobalObject.h +++ b/Libraries/LibJS/Runtime/GlobalObject.h @@ -27,13 +27,13 @@ #pragma once #include -#include +#include #include namespace JS { -class GlobalObject : public Object { - JS_OBJECT(GlobalObject, Object); +class GlobalObject : public ScopeObject { + JS_OBJECT(GlobalObject, ScopeObject); public: explicit GlobalObject(); @@ -41,6 +41,11 @@ public: virtual ~GlobalObject() override; + virtual Optional get_from_scope(const FlyString&) const override; + virtual void put_to_scope(const FlyString&, Variable) override; + virtual bool has_this_binding() const override; + virtual Value get_this_binding(GlobalObject&) const override; + Console& console() { return *m_console; } Shape* empty_object_shape() { return m_empty_object_shape; } @@ -66,6 +71,8 @@ protected: void add_constructor(const FlyString& property_name, ConstructorType*&, Object& prototype); private: + virtual bool is_global_object() const final { return true; } + JS_DECLARE_NATIVE_FUNCTION(gc); JS_DECLARE_NATIVE_FUNCTION(is_nan); JS_DECLARE_NATIVE_FUNCTION(is_finite); diff --git a/Libraries/LibJS/Runtime/LexicalEnvironment.cpp b/Libraries/LibJS/Runtime/LexicalEnvironment.cpp index 5a8157af2c6..2275b6402b3 100644 --- a/Libraries/LibJS/Runtime/LexicalEnvironment.cpp +++ b/Libraries/LibJS/Runtime/LexicalEnvironment.cpp @@ -34,23 +34,25 @@ namespace JS { LexicalEnvironment::LexicalEnvironment() + : ScopeObject(nullptr) { } LexicalEnvironment::LexicalEnvironment(EnvironmentRecordType environment_record_type) - : m_environment_record_type(environment_record_type) + : ScopeObject(nullptr) + , m_environment_record_type(environment_record_type) { } -LexicalEnvironment::LexicalEnvironment(HashMap variables, LexicalEnvironment* parent) - : m_parent(parent) +LexicalEnvironment::LexicalEnvironment(HashMap variables, ScopeObject* parent_scope) + : ScopeObject(parent_scope) , m_variables(move(variables)) { } -LexicalEnvironment::LexicalEnvironment(HashMap variables, LexicalEnvironment* parent, EnvironmentRecordType environment_record_type) - : m_environment_record_type(environment_record_type) - , m_parent(parent) +LexicalEnvironment::LexicalEnvironment(HashMap variables, ScopeObject* parent_scope, EnvironmentRecordType environment_record_type) + : ScopeObject(parent_scope) + , m_environment_record_type(environment_record_type) , m_variables(move(variables)) { } @@ -62,7 +64,6 @@ LexicalEnvironment::~LexicalEnvironment() void LexicalEnvironment::visit_edges(Visitor& visitor) { Cell::visit_edges(visitor); - visitor.visit(m_parent); visitor.visit(m_this_value); visitor.visit(m_home_object); visitor.visit(m_new_target); @@ -71,18 +72,14 @@ void LexicalEnvironment::visit_edges(Visitor& visitor) visitor.visit(it.value.value); } -Optional LexicalEnvironment::get(const FlyString& name) const +Optional LexicalEnvironment::get_from_scope(const FlyString& name) const { - ASSERT(type() != EnvironmentRecordType::Global); return m_variables.get(name); } -void LexicalEnvironment::set(GlobalObject& global_object, const FlyString& name, Variable variable) +void LexicalEnvironment::put_to_scope(const FlyString& name, Variable variable) { - if (type() == EnvironmentRecordType::Global) - global_object.put(name, variable.value); - else - m_variables.set(name, variable); + m_variables.set(name, variable); } bool LexicalEnvironment::has_super_binding() const @@ -108,7 +105,6 @@ bool LexicalEnvironment::has_this_binding() const case EnvironmentRecordType::Function: return this_binding_status() != ThisBindingStatus::Lexical; case EnvironmentRecordType::Module: - case EnvironmentRecordType::Global: return true; } ASSERT_NOT_REACHED(); diff --git a/Libraries/LibJS/Runtime/LexicalEnvironment.h b/Libraries/LibJS/Runtime/LexicalEnvironment.h index 28a5f2537ad..7dade90a8f6 100644 --- a/Libraries/LibJS/Runtime/LexicalEnvironment.h +++ b/Libraries/LibJS/Runtime/LexicalEnvironment.h @@ -28,17 +28,14 @@ #include #include -#include +#include #include namespace JS { -struct Variable { - Value value; - DeclarationKind declaration_kind; -}; +class LexicalEnvironment final : public ScopeObject { + JS_OBJECT(LexicalEnvironment, ScopeObject); -class LexicalEnvironment final : public Cell { public: enum class ThisBindingStatus { Lexical, @@ -49,21 +46,21 @@ public: enum class EnvironmentRecordType { Declarative, Function, - Global, Object, Module, }; LexicalEnvironment(); LexicalEnvironment(EnvironmentRecordType); - LexicalEnvironment(HashMap variables, LexicalEnvironment* parent); - LexicalEnvironment(HashMap variables, LexicalEnvironment* parent, EnvironmentRecordType); + LexicalEnvironment(HashMap variables, ScopeObject* parent_scope); + LexicalEnvironment(HashMap variables, ScopeObject* parent_scope, EnvironmentRecordType); virtual ~LexicalEnvironment() override; - LexicalEnvironment* parent() const { return m_parent; } - - Optional get(const FlyString&) const; - void set(GlobalObject&, const FlyString&, Variable); + // ^ScopeObject + virtual Optional get_from_scope(const FlyString&) const override; + virtual void put_to_scope(const FlyString&, Variable) override; + virtual bool has_this_binding() const override; + virtual Value get_this_binding(GlobalObject&) const override; void clear(); @@ -73,9 +70,7 @@ public: bool has_super_binding() const; Value get_super_base(); - bool has_this_binding() const; ThisBindingStatus this_binding_status() const { return m_this_binding_status; } - Value get_this_binding(GlobalObject&) const; void bind_this_value(GlobalObject&, Value this_value); // Not a standard operation. @@ -90,12 +85,11 @@ public: EnvironmentRecordType type() const { return m_environment_record_type; } private: - virtual const char* class_name() const override { return "LexicalEnvironment"; } + virtual bool is_lexical_environment() const final { return true; } virtual void visit_edges(Visitor&) override; EnvironmentRecordType m_environment_record_type : 8 { EnvironmentRecordType::Declarative }; ThisBindingStatus m_this_binding_status : 8 { ThisBindingStatus::Uninitialized }; - LexicalEnvironment* m_parent { nullptr }; HashMap m_variables; Value m_home_object; Value m_this_value; diff --git a/Libraries/LibJS/Runtime/Object.h b/Libraries/LibJS/Runtime/Object.h index a462c43b449..f871d141df2 100644 --- a/Libraries/LibJS/Runtime/Object.h +++ b/Libraries/LibJS/Runtime/Object.h @@ -127,6 +127,8 @@ public: virtual bool is_bigint_object() const { return false; } virtual bool is_string_iterator_object() const { return false; } virtual bool is_array_iterator_object() const { return false; } + virtual bool is_lexical_environment() const { return false; } + virtual bool is_global_object() const { return false; } virtual const char* class_name() const override { return "Object"; } virtual void visit_edges(Cell::Visitor&) override; diff --git a/Libraries/LibJS/Runtime/ScopeObject.cpp b/Libraries/LibJS/Runtime/ScopeObject.cpp new file mode 100644 index 00000000000..5a67011a550 --- /dev/null +++ b/Libraries/LibJS/Runtime/ScopeObject.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +namespace JS { + +ScopeObject::ScopeObject(ScopeObject* parent) + : Object(vm().scope_object_shape()) + , m_parent(parent) +{ +} + +ScopeObject::ScopeObject(GlobalObjectTag tag) + : Object(tag) +{ +} + +void ScopeObject::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_parent); +} + +} diff --git a/Libraries/LibJS/Runtime/ScopeObject.h b/Libraries/LibJS/Runtime/ScopeObject.h new file mode 100644 index 00000000000..ac29c115030 --- /dev/null +++ b/Libraries/LibJS/Runtime/ScopeObject.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include + +namespace JS { + +struct Variable { + Value value; + DeclarationKind declaration_kind; +}; + +class ScopeObject : public Object { + JS_OBJECT(ScopeObject, Object); + +public: + virtual Optional get_from_scope(const FlyString&) const = 0; + virtual void put_to_scope(const FlyString&, Variable) = 0; + virtual bool has_this_binding() const = 0; + virtual Value get_this_binding(GlobalObject&) const = 0; + + ScopeObject* parent() { return m_parent; } + const ScopeObject* parent() const { return m_parent; } + +protected: + explicit ScopeObject(ScopeObject* parent); + explicit ScopeObject(GlobalObjectTag); + + virtual void visit_edges(Visitor&) override; + +private: + ScopeObject* m_parent { nullptr }; +}; + +} diff --git a/Libraries/LibJS/Runtime/ScriptFunction.cpp b/Libraries/LibJS/Runtime/ScriptFunction.cpp index c8b1d7be6f8..5b751be4236 100644 --- a/Libraries/LibJS/Runtime/ScriptFunction.cpp +++ b/Libraries/LibJS/Runtime/ScriptFunction.cpp @@ -47,17 +47,17 @@ static ScriptFunction* typed_this(VM& vm, GlobalObject& global_object) return static_cast(this_object); } -ScriptFunction* ScriptFunction::create(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector parameters, i32 m_function_length, LexicalEnvironment* parent_environment, bool is_strict, bool is_arrow_function) +ScriptFunction* ScriptFunction::create(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector parameters, i32 m_function_length, ScopeObject* parent_scope, bool is_strict, bool is_arrow_function) { - return global_object.heap().allocate(global_object, global_object, name, body, move(parameters), m_function_length, parent_environment, *global_object.function_prototype(), is_strict, is_arrow_function); + return global_object.heap().allocate(global_object, global_object, name, body, move(parameters), m_function_length, parent_scope, *global_object.function_prototype(), is_strict, is_arrow_function); } -ScriptFunction::ScriptFunction(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector parameters, i32 m_function_length, LexicalEnvironment* parent_environment, Object& prototype, bool is_strict, bool is_arrow_function) +ScriptFunction::ScriptFunction(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector parameters, i32 m_function_length, ScopeObject* parent_scope, Object& prototype, bool is_strict, bool is_arrow_function) : Function(prototype, is_arrow_function ? vm().this_value(global_object) : Value(), {}) , m_name(name) , m_body(body) , m_parameters(move(parameters)) - , m_parent_environment(parent_environment) + , m_parent_scope(parent_scope) , m_function_length(m_function_length) , m_is_strict(is_strict) , m_is_arrow_function(is_arrow_function) @@ -84,7 +84,7 @@ ScriptFunction::~ScriptFunction() void ScriptFunction::visit_edges(Visitor& visitor) { Function::visit_edges(visitor); - visitor.visit(m_parent_environment); + visitor.visit(m_parent_scope); } LexicalEnvironment* ScriptFunction::create_environment() @@ -102,11 +102,13 @@ LexicalEnvironment* ScriptFunction::create_environment() } } - auto* environment = heap().allocate(global_object(), move(variables), m_parent_environment, LexicalEnvironment::EnvironmentRecordType::Function); + auto* environment = heap().allocate(global_object(), move(variables), m_parent_scope, LexicalEnvironment::EnvironmentRecordType::Function); environment->set_home_object(home_object()); environment->set_current_function(*this); - if (m_is_arrow_function) - environment->set_new_target(m_parent_environment->new_target()); + if (m_is_arrow_function) { + if (m_parent_scope->is_lexical_environment()) + environment->set_new_target(static_cast(m_parent_scope)->new_target()); + } return environment; } @@ -144,7 +146,7 @@ Value ScriptFunction::execute_function_body() argument_value = js_undefined(); } arguments.append({ parameter.name, argument_value }); - vm.current_environment()->set(global_object(), parameter.name, { argument_value, DeclarationKind::Var }); + vm.current_scope()->put_to_scope(parameter.name, { argument_value, DeclarationKind::Var }); } return interpreter->execute_statement(global_object(), m_body, move(arguments), ScopeType::Function); diff --git a/Libraries/LibJS/Runtime/ScriptFunction.h b/Libraries/LibJS/Runtime/ScriptFunction.h index 346d6e82193..cefb2e58e9b 100644 --- a/Libraries/LibJS/Runtime/ScriptFunction.h +++ b/Libraries/LibJS/Runtime/ScriptFunction.h @@ -35,9 +35,9 @@ class ScriptFunction final : public Function { JS_OBJECT(ScriptFunction, Function); public: - static ScriptFunction* create(GlobalObject&, const FlyString& name, const Statement& body, Vector parameters, i32 m_function_length, LexicalEnvironment* parent_environment, bool is_strict, bool is_arrow_function = false); + static ScriptFunction* create(GlobalObject&, const FlyString& name, const Statement& body, Vector parameters, i32 m_function_length, ScopeObject* parent_scope, bool is_strict, bool is_arrow_function = false); - ScriptFunction(GlobalObject&, const FlyString& name, const Statement& body, Vector parameters, i32 m_function_length, LexicalEnvironment* parent_environment, Object& prototype, bool is_strict, bool is_arrow_function = false); + ScriptFunction(GlobalObject&, const FlyString& name, const Statement& body, Vector parameters, i32 m_function_length, ScopeObject* parent_scope, Object& prototype, bool is_strict, bool is_arrow_function = false); virtual void initialize(GlobalObject&) override; virtual ~ScriptFunction(); @@ -68,7 +68,7 @@ private: FlyString m_name; NonnullRefPtr m_body; const Vector m_parameters; - LexicalEnvironment* m_parent_environment { nullptr }; + ScopeObject* m_parent_scope { nullptr }; i32 m_function_length { 0 }; bool m_is_strict { false }; bool m_is_arrow_function { false }; diff --git a/Libraries/LibJS/Runtime/VM.cpp b/Libraries/LibJS/Runtime/VM.cpp index 278b0c68bbc..953aae1f519 100644 --- a/Libraries/LibJS/Runtime/VM.cpp +++ b/Libraries/LibJS/Runtime/VM.cpp @@ -51,6 +51,8 @@ VM::VM() m_single_ascii_character_strings[i] = m_heap.allocate_without_global_object(String::format("%c", i)); } + m_scope_object_shape = m_heap.allocate_without_global_object(Shape::ShapeWithoutGlobalObjectTag::Tag); + #define __JS_ENUMERATE(SymbolName, snake_name) \ m_well_known_symbol_##snake_name = js_symbol(*this, "Symbol." #SymbolName, false); JS_ENUMERATE_WELL_KNOWN_SYMBOLS @@ -103,6 +105,8 @@ void VM::gather_roots(HashTable& roots) for (auto* string : m_single_ascii_character_strings) roots.set(string); + roots.set(m_scope_object_shape); + if (m_exception) roots.set(m_exception); @@ -116,7 +120,7 @@ void VM::gather_roots(HashTable& roots) if (argument.is_cell()) roots.set(argument.as_cell()); } - roots.set(call_frame->environment); + roots.set(call_frame->scope); } #define __JS_ENUMERATE(SymbolName, snake_name) \ @@ -142,17 +146,15 @@ Symbol* VM::get_global_symbol(const String& description) void VM::set_variable(const FlyString& name, Value value, GlobalObject& global_object, bool first_assignment) { if (m_call_stack.size()) { - for (auto* environment = current_environment(); environment; environment = environment->parent()) { - if (environment->type() == LexicalEnvironment::EnvironmentRecordType::Global) - break; - auto possible_match = environment->get(name); + for (auto* scope = current_scope(); scope; scope = scope->parent()) { + auto possible_match = scope->get_from_scope(name); if (possible_match.has_value()) { if (!first_assignment && possible_match.value().declaration_kind == DeclarationKind::Const) { throw_exception(global_object, ErrorType::InvalidAssignToConst); return; } - environment->set(global_object, name, { value, possible_match.value().declaration_kind }); + scope->put_to_scope(name, { value, possible_match.value().declaration_kind }); return; } } @@ -164,10 +166,8 @@ void VM::set_variable(const FlyString& name, Value value, GlobalObject& global_o Value VM::get_variable(const FlyString& name, GlobalObject& global_object) { if (m_call_stack.size()) { - for (auto* environment = current_environment(); environment; environment = environment->parent()) { - if (environment->type() == LexicalEnvironment::EnvironmentRecordType::Global) - break; - auto possible_match = environment->get(name); + for (auto* scope = current_scope(); scope; scope = scope->parent()) { + auto possible_match = scope->get_from_scope(name); if (possible_match.has_value()) return possible_match.value().value; } @@ -181,10 +181,10 @@ Value VM::get_variable(const FlyString& name, GlobalObject& global_object) Reference VM::get_reference(const FlyString& name) { if (m_call_stack.size()) { - for (auto* environment = current_environment(); environment; environment = environment->parent()) { - if (environment->type() == LexicalEnvironment::EnvironmentRecordType::Global) + for (auto* scope = current_scope(); scope; scope = scope->parent()) { + if (scope->is_global_object()) break; - auto possible_match = environment->get(name); + auto possible_match = scope->get_from_scope(name); if (possible_match.has_value()) return { Reference::LocalVariable, name }; } @@ -208,13 +208,14 @@ Value VM::construct(Function& function, Function& new_target, Optionalset_new_target(&new_target); + auto* environment = function.create_environment(); + call_frame.scope = environment; + environment->set_new_target(&new_target); Object* new_object = nullptr; if (function.constructor_kind() == Function::ConstructorKind::Base) { new_object = Object::create_empty(global_object); - call_frame.environment->bind_this_value(global_object, new_object); + environment->bind_this_value(global_object, new_object); if (exception()) return {}; auto prototype = new_target.get(names.prototype); @@ -232,14 +233,15 @@ Value VM::construct(Function& function, Function& new_target, Optionalget_this_binding(global_object); + this_value = call_frame.scope->get_this_binding(global_object); pop_call_frame(); call_frame_popper.disarm(); // If we are constructing an instance of a derived class, // set the prototype on objects created by constructors that return an object (i.e. NativeFunction subclasses). if (function.constructor_kind() == Function::ConstructorKind::Base && new_target.constructor_kind() == Function::ConstructorKind::Derived && result.is_object()) { - current_environment()->replace_this_binding(result); + ASSERT(current_scope()->is_lexical_environment()); + static_cast(current_scope())->replace_this_binding(result); auto prototype = new_target.get(names.prototype); if (exception()) return {}; @@ -292,22 +294,23 @@ String VM::join_arguments() const Value VM::resolve_this_binding(GlobalObject& global_object) const { - return get_this_environment()->get_this_binding(global_object); + return find_this_scope()->get_this_binding(global_object); } -const LexicalEnvironment* VM::get_this_environment() const +const ScopeObject* VM::find_this_scope() const { // We will always return because the Global environment will always be reached, which has a |this| binding. - for (const LexicalEnvironment* environment = current_environment(); environment; environment = environment->parent()) { - if (environment->has_this_binding()) - return environment; + for (auto* scope = current_scope(); scope; scope = scope->parent()) { + if (scope->has_this_binding()) + return scope; } ASSERT_NOT_REACHED(); } Value VM::get_new_target() const { - return get_this_environment()->new_target(); + ASSERT(find_this_scope()->is_lexical_environment()); + return static_cast(find_this_scope())->new_target(); } Value VM::call_internal(Function& function, Value this_value, Optional arguments) @@ -321,10 +324,11 @@ Value VM::call_internal(Function& function, Value this_value, Optionalthis_binding_status() == LexicalEnvironment::ThisBindingStatus::Uninitialized); - call_frame.environment->bind_this_value(function.global_object(), call_frame.this_value); + ASSERT(environment->this_binding_status() == LexicalEnvironment::ThisBindingStatus::Uninitialized); + environment->bind_this_value(function.global_object(), call_frame.this_value); if (exception()) return {}; diff --git a/Libraries/LibJS/Runtime/VM.h b/Libraries/LibJS/Runtime/VM.h index 16e1a0213d4..b2fc9b9ac75 100644 --- a/Libraries/LibJS/Runtime/VM.h +++ b/Libraries/LibJS/Runtime/VM.h @@ -59,7 +59,7 @@ struct CallFrame { FlyString function_name; Value this_value; Vector arguments; - LexicalEnvironment* environment { nullptr }; + ScopeObject* scope { nullptr }; bool is_strict_mode { false }; }; @@ -134,8 +134,8 @@ public: const Vector& call_stack() const { return m_call_stack; } Vector& call_stack() { return m_call_stack; } - const LexicalEnvironment* current_environment() const { return call_frame().environment; } - LexicalEnvironment* current_environment() { return call_frame().environment; } + const ScopeObject* current_scope() const { return call_frame().scope; } + ScopeObject* current_scope() { return call_frame().scope; } bool in_strict_mode() const; @@ -222,7 +222,7 @@ public: String join_arguments() const; Value resolve_this_binding(GlobalObject&) const; - const LexicalEnvironment* get_this_environment() const; + const ScopeObject* find_this_scope() const; Value get_new_target() const; template @@ -242,6 +242,8 @@ public: CommonPropertyNames names; + Shape& scope_object_shape() { return *m_scope_object_shape; } + private: VM(); @@ -271,6 +273,8 @@ private: Symbol* m_well_known_symbol_##snake_name { nullptr }; JS_ENUMERATE_WELL_KNOWN_SYMBOLS #undef __JS_ENUMERATE + + Shape* m_scope_object_shape { nullptr }; }; template<>