1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 13:48:12 +00:00

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. :^)
This commit is contained in:
Andreas Kling 2020-11-28 16:02:27 +01:00
parent e1bbc7c075
commit c3fe9b4df8
16 changed files with 241 additions and 92 deletions

View file

@ -101,7 +101,7 @@ Value FunctionDeclaration::execute(Interpreter&, GlobalObject&) const
Value FunctionExpression::execute(Interpreter& interpreter, GlobalObject& global_object) 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 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()) { if (m_callee->is_member_expression()) {
auto& member_expression = static_cast<const MemberExpression&>(*m_callee); auto& member_expression = static_cast<const MemberExpression&>(*m_callee);
bool is_super_property_lookup = member_expression.object().is_super_expression(); 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()) if (vm.exception())
return {}; return {};
if (is_super_property_lookup && lookup_target.is_nullish()) { 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()) if (result.is_object())
new_object = &result.as_object(); new_object = &result.as_object();
} else if (m_callee->is_super_expression()) { } 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. // FIXME: Functions should track their constructor kind.
if (!super_constructor || !super_constructor->is_function()) { if (!super_constructor || !super_constructor->is_function()) {
vm.throw_exception<TypeError>(global_object, ErrorType::NotAConstructor, "Super constructor"); vm.throw_exception<TypeError>(global_object, ErrorType::NotAConstructor, "Super constructor");
@ -215,7 +215,7 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj
if (vm.exception()) if (vm.exception())
return {}; return {};
vm.current_environment()->bind_this_value(global_object, result); interpreter.current_environment()->bind_this_value(global_object, result);
} else { } else {
result = vm.call(function, this_value, move(arguments)); result = vm.call(function, this_value, move(arguments));
} }
@ -801,7 +801,7 @@ Value ClassDeclaration::execute(Interpreter& interpreter, GlobalObject& global_o
if (interpreter.exception()) if (interpreter.exception())
return {}; 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(); return js_undefined();
} }

View file

@ -61,6 +61,7 @@ set(SOURCES
Runtime/RegExpConstructor.cpp Runtime/RegExpConstructor.cpp
Runtime/RegExpObject.cpp Runtime/RegExpObject.cpp
Runtime/RegExpPrototype.cpp Runtime/RegExpPrototype.cpp
Runtime/ScopeObject.cpp
Runtime/ScriptFunction.cpp Runtime/ScriptFunction.cpp
Runtime/Shape.cpp Runtime/Shape.cpp
Runtime/StringConstructor.cpp Runtime/StringConstructor.cpp

View file

@ -124,6 +124,7 @@ class NativeProperty;
class PrimitiveString; class PrimitiveString;
class Reference; class Reference;
class ScopeNode; class ScopeNode;
class ScopeObject;
class Shape; class Shape;
class Statement; class Statement;
class Symbol; class Symbol;

View file

@ -72,8 +72,7 @@ Value Interpreter::run(GlobalObject& global_object, const Program& program)
global_call_frame.this_value = &global_object; global_call_frame.this_value = &global_object;
static FlyString global_execution_context_name = "(global execution context)"; static FlyString global_execution_context_name = "(global execution context)";
global_call_frame.function_name = global_execution_context_name; global_call_frame.function_name = global_execution_context_name;
global_call_frame.environment = heap().allocate<LexicalEnvironment>(global_object, LexicalEnvironment::EnvironmentRecordType::Global); global_call_frame.scope = &global_object;
global_call_frame.environment->bind_this_value(global_object, &global_object);
ASSERT(!vm.exception()); ASSERT(!vm.exception());
global_call_frame.is_strict_mode = program.is_strict_mode(); global_call_frame.is_strict_mode = program.is_strict_mode();
vm.push_call_frame(global_call_frame, global_object); 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) void Interpreter::enter_scope(const ScopeNode& scope_node, ArgumentVector arguments, ScopeType scope_type, GlobalObject& global_object)
{ {
for (auto& declaration : scope_node.functions()) { 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); 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; bool pushed_lexical_environment = false;
if (!scope_variables_with_declaration_kind.is_empty()) { if (!scope_variables_with_declaration_kind.is_empty()) {
auto* block_lexical_environment = heap().allocate<LexicalEnvironment>(global_object, move(scope_variables_with_declaration_kind), current_environment()); auto* block_lexical_environment = heap().allocate<LexicalEnvironment>(global_object, move(scope_variables_with_declaration_kind), current_scope());
vm().call_frame().environment = block_lexical_environment; vm().call_frame().scope = block_lexical_environment;
pushed_lexical_environment = true; pushed_lexical_environment = true;
} }
@ -140,7 +139,7 @@ void Interpreter::exit_scope(const ScopeNode& scope_node)
while (!m_scope_stack.is_empty()) { while (!m_scope_stack.is_empty()) {
auto popped_scope = m_scope_stack.take_last(); auto popped_scope = m_scope_stack.take_last();
if (popped_scope.pushed_environment) 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) if (popped_scope.scope_node.ptr() == &scope_node)
break; break;
} }
@ -185,4 +184,10 @@ Value Interpreter::execute_statement(GlobalObject& global_object, const Statemen
return did_return ? vm().last_value() : js_undefined(); return did_return ? vm().last_value() : js_undefined();
} }
LexicalEnvironment* Interpreter::current_environment()
{
ASSERT(vm().call_frame().scope->is_lexical_environment());
return static_cast<LexicalEnvironment*>(vm().call_frame().scope);
}
} }

View file

@ -71,7 +71,8 @@ public:
Heap& heap() { return vm().heap(); } Heap& heap() { return vm().heap(); }
Exception* exception() { return vm().exception(); } 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 enter_scope(const ScopeNode&, ArgumentVector, ScopeType, GlobalObject&);
void exit_scope(const ScopeNode&); void exit_scope(const ScopeNode&);

View file

@ -68,7 +68,7 @@
namespace JS { namespace JS {
GlobalObject::GlobalObject() GlobalObject::GlobalObject()
: Object(GlobalObjectTag::Tag) : ScopeObject(GlobalObjectTag::Tag)
, m_console(make<Console>(*this)) , m_console(make<Console>(*this))
{ {
} }
@ -149,7 +149,7 @@ GlobalObject::~GlobalObject()
void GlobalObject::visit_edges(Visitor& visitor) void GlobalObject::visit_edges(Visitor& visitor)
{ {
Object::visit_edges(visitor); Base::visit_edges(visitor);
visitor.visit(m_empty_object_shape); visitor.visit(m_empty_object_shape);
visitor.visit(m_new_object_shape); visitor.visit(m_new_object_shape);
@ -205,4 +205,27 @@ JS_DEFINE_NATIVE_FUNCTION(GlobalObject::parse_float)
return js_nan(); return js_nan();
} }
Optional<Variable> 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);
}
} }

View file

@ -27,13 +27,13 @@
#pragma once #pragma once
#include <LibJS/Heap/Heap.h> #include <LibJS/Heap/Heap.h>
#include <LibJS/Runtime/Object.h> #include <LibJS/Runtime/ScopeObject.h>
#include <LibJS/Runtime/VM.h> #include <LibJS/Runtime/VM.h>
namespace JS { namespace JS {
class GlobalObject : public Object { class GlobalObject : public ScopeObject {
JS_OBJECT(GlobalObject, Object); JS_OBJECT(GlobalObject, ScopeObject);
public: public:
explicit GlobalObject(); explicit GlobalObject();
@ -41,6 +41,11 @@ public:
virtual ~GlobalObject() override; virtual ~GlobalObject() override;
virtual Optional<Variable> 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; } Console& console() { return *m_console; }
Shape* empty_object_shape() { return m_empty_object_shape; } Shape* empty_object_shape() { return m_empty_object_shape; }
@ -66,6 +71,8 @@ protected:
void add_constructor(const FlyString& property_name, ConstructorType*&, Object& prototype); void add_constructor(const FlyString& property_name, ConstructorType*&, Object& prototype);
private: private:
virtual bool is_global_object() const final { return true; }
JS_DECLARE_NATIVE_FUNCTION(gc); JS_DECLARE_NATIVE_FUNCTION(gc);
JS_DECLARE_NATIVE_FUNCTION(is_nan); JS_DECLARE_NATIVE_FUNCTION(is_nan);
JS_DECLARE_NATIVE_FUNCTION(is_finite); JS_DECLARE_NATIVE_FUNCTION(is_finite);

View file

@ -34,23 +34,25 @@
namespace JS { namespace JS {
LexicalEnvironment::LexicalEnvironment() LexicalEnvironment::LexicalEnvironment()
: ScopeObject(nullptr)
{ {
} }
LexicalEnvironment::LexicalEnvironment(EnvironmentRecordType environment_record_type) 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<FlyString, Variable> variables, LexicalEnvironment* parent) LexicalEnvironment::LexicalEnvironment(HashMap<FlyString, Variable> variables, ScopeObject* parent_scope)
: m_parent(parent) : ScopeObject(parent_scope)
, m_variables(move(variables)) , m_variables(move(variables))
{ {
} }
LexicalEnvironment::LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent, EnvironmentRecordType environment_record_type) LexicalEnvironment::LexicalEnvironment(HashMap<FlyString, Variable> variables, ScopeObject* parent_scope, EnvironmentRecordType environment_record_type)
: m_environment_record_type(environment_record_type) : ScopeObject(parent_scope)
, m_parent(parent) , m_environment_record_type(environment_record_type)
, m_variables(move(variables)) , m_variables(move(variables))
{ {
} }
@ -62,7 +64,6 @@ LexicalEnvironment::~LexicalEnvironment()
void LexicalEnvironment::visit_edges(Visitor& visitor) void LexicalEnvironment::visit_edges(Visitor& visitor)
{ {
Cell::visit_edges(visitor); Cell::visit_edges(visitor);
visitor.visit(m_parent);
visitor.visit(m_this_value); visitor.visit(m_this_value);
visitor.visit(m_home_object); visitor.visit(m_home_object);
visitor.visit(m_new_target); visitor.visit(m_new_target);
@ -71,18 +72,14 @@ void LexicalEnvironment::visit_edges(Visitor& visitor)
visitor.visit(it.value.value); visitor.visit(it.value.value);
} }
Optional<Variable> LexicalEnvironment::get(const FlyString& name) const Optional<Variable> LexicalEnvironment::get_from_scope(const FlyString& name) const
{ {
ASSERT(type() != EnvironmentRecordType::Global);
return m_variables.get(name); 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) m_variables.set(name, variable);
global_object.put(name, variable.value);
else
m_variables.set(name, variable);
} }
bool LexicalEnvironment::has_super_binding() const bool LexicalEnvironment::has_super_binding() const
@ -108,7 +105,6 @@ bool LexicalEnvironment::has_this_binding() const
case EnvironmentRecordType::Function: case EnvironmentRecordType::Function:
return this_binding_status() != ThisBindingStatus::Lexical; return this_binding_status() != ThisBindingStatus::Lexical;
case EnvironmentRecordType::Module: case EnvironmentRecordType::Module:
case EnvironmentRecordType::Global:
return true; return true;
} }
ASSERT_NOT_REACHED(); ASSERT_NOT_REACHED();

View file

@ -28,17 +28,14 @@
#include <AK/FlyString.h> #include <AK/FlyString.h>
#include <AK/HashMap.h> #include <AK/HashMap.h>
#include <LibJS/Runtime/Cell.h> #include <LibJS/Runtime/ScopeObject.h>
#include <LibJS/Runtime/Value.h> #include <LibJS/Runtime/Value.h>
namespace JS { namespace JS {
struct Variable { class LexicalEnvironment final : public ScopeObject {
Value value; JS_OBJECT(LexicalEnvironment, ScopeObject);
DeclarationKind declaration_kind;
};
class LexicalEnvironment final : public Cell {
public: public:
enum class ThisBindingStatus { enum class ThisBindingStatus {
Lexical, Lexical,
@ -49,21 +46,21 @@ public:
enum class EnvironmentRecordType { enum class EnvironmentRecordType {
Declarative, Declarative,
Function, Function,
Global,
Object, Object,
Module, Module,
}; };
LexicalEnvironment(); LexicalEnvironment();
LexicalEnvironment(EnvironmentRecordType); LexicalEnvironment(EnvironmentRecordType);
LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent); LexicalEnvironment(HashMap<FlyString, Variable> variables, ScopeObject* parent_scope);
LexicalEnvironment(HashMap<FlyString, Variable> variables, LexicalEnvironment* parent, EnvironmentRecordType); LexicalEnvironment(HashMap<FlyString, Variable> variables, ScopeObject* parent_scope, EnvironmentRecordType);
virtual ~LexicalEnvironment() override; virtual ~LexicalEnvironment() override;
LexicalEnvironment* parent() const { return m_parent; } // ^ScopeObject
virtual Optional<Variable> get_from_scope(const FlyString&) const override;
Optional<Variable> get(const FlyString&) const; virtual void put_to_scope(const FlyString&, Variable) override;
void set(GlobalObject&, const FlyString&, Variable); virtual bool has_this_binding() const override;
virtual Value get_this_binding(GlobalObject&) const override;
void clear(); void clear();
@ -73,9 +70,7 @@ public:
bool has_super_binding() const; bool has_super_binding() const;
Value get_super_base(); Value get_super_base();
bool has_this_binding() const;
ThisBindingStatus this_binding_status() const { return m_this_binding_status; } ThisBindingStatus this_binding_status() const { return m_this_binding_status; }
Value get_this_binding(GlobalObject&) const;
void bind_this_value(GlobalObject&, Value this_value); void bind_this_value(GlobalObject&, Value this_value);
// Not a standard operation. // Not a standard operation.
@ -90,12 +85,11 @@ public:
EnvironmentRecordType type() const { return m_environment_record_type; } EnvironmentRecordType type() const { return m_environment_record_type; }
private: 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; virtual void visit_edges(Visitor&) override;
EnvironmentRecordType m_environment_record_type : 8 { EnvironmentRecordType::Declarative }; EnvironmentRecordType m_environment_record_type : 8 { EnvironmentRecordType::Declarative };
ThisBindingStatus m_this_binding_status : 8 { ThisBindingStatus::Uninitialized }; ThisBindingStatus m_this_binding_status : 8 { ThisBindingStatus::Uninitialized };
LexicalEnvironment* m_parent { nullptr };
HashMap<FlyString, Variable> m_variables; HashMap<FlyString, Variable> m_variables;
Value m_home_object; Value m_home_object;
Value m_this_value; Value m_this_value;

View file

@ -127,6 +127,8 @@ public:
virtual bool is_bigint_object() const { return false; } virtual bool is_bigint_object() const { return false; }
virtual bool is_string_iterator_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_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 const char* class_name() const override { return "Object"; }
virtual void visit_edges(Cell::Visitor&) override; virtual void visit_edges(Cell::Visitor&) override;

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* 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 <LibJS/Runtime/ScopeObject.h>
#include <LibJS/Runtime/VM.h>
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);
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* 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 <LibJS/Runtime/Object.h>
namespace JS {
struct Variable {
Value value;
DeclarationKind declaration_kind;
};
class ScopeObject : public Object {
JS_OBJECT(ScopeObject, Object);
public:
virtual Optional<Variable> 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 };
};
}

View file

@ -47,17 +47,17 @@ static ScriptFunction* typed_this(VM& vm, GlobalObject& global_object)
return static_cast<ScriptFunction*>(this_object); return static_cast<ScriptFunction*>(this_object);
} }
ScriptFunction* ScriptFunction::create(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> 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<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, bool is_strict, bool is_arrow_function)
{ {
return global_object.heap().allocate<ScriptFunction>(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<ScriptFunction>(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<FunctionNode::Parameter> 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<FunctionNode::Parameter> 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(), {}) : Function(prototype, is_arrow_function ? vm().this_value(global_object) : Value(), {})
, m_name(name) , m_name(name)
, m_body(body) , m_body(body)
, m_parameters(move(parameters)) , m_parameters(move(parameters))
, m_parent_environment(parent_environment) , m_parent_scope(parent_scope)
, m_function_length(m_function_length) , m_function_length(m_function_length)
, m_is_strict(is_strict) , m_is_strict(is_strict)
, m_is_arrow_function(is_arrow_function) , m_is_arrow_function(is_arrow_function)
@ -84,7 +84,7 @@ ScriptFunction::~ScriptFunction()
void ScriptFunction::visit_edges(Visitor& visitor) void ScriptFunction::visit_edges(Visitor& visitor)
{ {
Function::visit_edges(visitor); Function::visit_edges(visitor);
visitor.visit(m_parent_environment); visitor.visit(m_parent_scope);
} }
LexicalEnvironment* ScriptFunction::create_environment() LexicalEnvironment* ScriptFunction::create_environment()
@ -102,11 +102,13 @@ LexicalEnvironment* ScriptFunction::create_environment()
} }
} }
auto* environment = heap().allocate<LexicalEnvironment>(global_object(), move(variables), m_parent_environment, LexicalEnvironment::EnvironmentRecordType::Function); auto* environment = heap().allocate<LexicalEnvironment>(global_object(), move(variables), m_parent_scope, LexicalEnvironment::EnvironmentRecordType::Function);
environment->set_home_object(home_object()); environment->set_home_object(home_object());
environment->set_current_function(*this); environment->set_current_function(*this);
if (m_is_arrow_function) if (m_is_arrow_function) {
environment->set_new_target(m_parent_environment->new_target()); if (m_parent_scope->is_lexical_environment())
environment->set_new_target(static_cast<LexicalEnvironment*>(m_parent_scope)->new_target());
}
return environment; return environment;
} }
@ -144,7 +146,7 @@ Value ScriptFunction::execute_function_body()
argument_value = js_undefined(); argument_value = js_undefined();
} }
arguments.append({ parameter.name, argument_value }); 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); return interpreter->execute_statement(global_object(), m_body, move(arguments), ScopeType::Function);

View file

@ -35,9 +35,9 @@ class ScriptFunction final : public Function {
JS_OBJECT(ScriptFunction, Function); JS_OBJECT(ScriptFunction, Function);
public: public:
static ScriptFunction* create(GlobalObject&, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> 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<FunctionNode::Parameter> 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<FunctionNode::Parameter> 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<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, Object& prototype, bool is_strict, bool is_arrow_function = false);
virtual void initialize(GlobalObject&) override; virtual void initialize(GlobalObject&) override;
virtual ~ScriptFunction(); virtual ~ScriptFunction();
@ -68,7 +68,7 @@ private:
FlyString m_name; FlyString m_name;
NonnullRefPtr<Statement> m_body; NonnullRefPtr<Statement> m_body;
const Vector<FunctionNode::Parameter> m_parameters; const Vector<FunctionNode::Parameter> m_parameters;
LexicalEnvironment* m_parent_environment { nullptr }; ScopeObject* m_parent_scope { nullptr };
i32 m_function_length { 0 }; i32 m_function_length { 0 };
bool m_is_strict { false }; bool m_is_strict { false };
bool m_is_arrow_function { false }; bool m_is_arrow_function { false };

View file

@ -51,6 +51,8 @@ VM::VM()
m_single_ascii_character_strings[i] = m_heap.allocate_without_global_object<PrimitiveString>(String::format("%c", i)); m_single_ascii_character_strings[i] = m_heap.allocate_without_global_object<PrimitiveString>(String::format("%c", i));
} }
m_scope_object_shape = m_heap.allocate_without_global_object<Shape>(Shape::ShapeWithoutGlobalObjectTag::Tag);
#define __JS_ENUMERATE(SymbolName, snake_name) \ #define __JS_ENUMERATE(SymbolName, snake_name) \
m_well_known_symbol_##snake_name = js_symbol(*this, "Symbol." #SymbolName, false); m_well_known_symbol_##snake_name = js_symbol(*this, "Symbol." #SymbolName, false);
JS_ENUMERATE_WELL_KNOWN_SYMBOLS JS_ENUMERATE_WELL_KNOWN_SYMBOLS
@ -103,6 +105,8 @@ void VM::gather_roots(HashTable<Cell*>& roots)
for (auto* string : m_single_ascii_character_strings) for (auto* string : m_single_ascii_character_strings)
roots.set(string); roots.set(string);
roots.set(m_scope_object_shape);
if (m_exception) if (m_exception)
roots.set(m_exception); roots.set(m_exception);
@ -116,7 +120,7 @@ void VM::gather_roots(HashTable<Cell*>& roots)
if (argument.is_cell()) if (argument.is_cell())
roots.set(argument.as_cell()); roots.set(argument.as_cell());
} }
roots.set(call_frame->environment); roots.set(call_frame->scope);
} }
#define __JS_ENUMERATE(SymbolName, snake_name) \ #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) void VM::set_variable(const FlyString& name, Value value, GlobalObject& global_object, bool first_assignment)
{ {
if (m_call_stack.size()) { if (m_call_stack.size()) {
for (auto* environment = current_environment(); environment; environment = environment->parent()) { for (auto* scope = current_scope(); scope; scope = scope->parent()) {
if (environment->type() == LexicalEnvironment::EnvironmentRecordType::Global) auto possible_match = scope->get_from_scope(name);
break;
auto possible_match = environment->get(name);
if (possible_match.has_value()) { if (possible_match.has_value()) {
if (!first_assignment && possible_match.value().declaration_kind == DeclarationKind::Const) { if (!first_assignment && possible_match.value().declaration_kind == DeclarationKind::Const) {
throw_exception<TypeError>(global_object, ErrorType::InvalidAssignToConst); throw_exception<TypeError>(global_object, ErrorType::InvalidAssignToConst);
return; return;
} }
environment->set(global_object, name, { value, possible_match.value().declaration_kind }); scope->put_to_scope(name, { value, possible_match.value().declaration_kind });
return; 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) Value VM::get_variable(const FlyString& name, GlobalObject& global_object)
{ {
if (m_call_stack.size()) { if (m_call_stack.size()) {
for (auto* environment = current_environment(); environment; environment = environment->parent()) { for (auto* scope = current_scope(); scope; scope = scope->parent()) {
if (environment->type() == LexicalEnvironment::EnvironmentRecordType::Global) auto possible_match = scope->get_from_scope(name);
break;
auto possible_match = environment->get(name);
if (possible_match.has_value()) if (possible_match.has_value())
return possible_match.value().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) Reference VM::get_reference(const FlyString& name)
{ {
if (m_call_stack.size()) { if (m_call_stack.size()) {
for (auto* environment = current_environment(); environment; environment = environment->parent()) { for (auto* scope = current_scope(); scope; scope = scope->parent()) {
if (environment->type() == LexicalEnvironment::EnvironmentRecordType::Global) if (scope->is_global_object())
break; break;
auto possible_match = environment->get(name); auto possible_match = scope->get_from_scope(name);
if (possible_match.has_value()) if (possible_match.has_value())
return { Reference::LocalVariable, name }; return { Reference::LocalVariable, name };
} }
@ -208,13 +208,14 @@ Value VM::construct(Function& function, Function& new_target, Optional<MarkedVal
call_frame.arguments = function.bound_arguments(); call_frame.arguments = function.bound_arguments();
if (arguments.has_value()) if (arguments.has_value())
call_frame.arguments.append(arguments.value().values()); call_frame.arguments.append(arguments.value().values());
call_frame.environment = function.create_environment(); auto* environment = function.create_environment();
call_frame.environment->set_new_target(&new_target); call_frame.scope = environment;
environment->set_new_target(&new_target);
Object* new_object = nullptr; Object* new_object = nullptr;
if (function.constructor_kind() == Function::ConstructorKind::Base) { if (function.constructor_kind() == Function::ConstructorKind::Base) {
new_object = Object::create_empty(global_object); 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()) if (exception())
return {}; return {};
auto prototype = new_target.get(names.prototype); auto prototype = new_target.get(names.prototype);
@ -232,14 +233,15 @@ Value VM::construct(Function& function, Function& new_target, Optional<MarkedVal
call_frame.this_value = this_value; call_frame.this_value = this_value;
auto result = function.construct(new_target); auto result = function.construct(new_target);
this_value = call_frame.environment->get_this_binding(global_object); this_value = call_frame.scope->get_this_binding(global_object);
pop_call_frame(); pop_call_frame();
call_frame_popper.disarm(); call_frame_popper.disarm();
// If we are constructing an instance of a derived class, // 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). // 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()) { 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<LexicalEnvironment*>(current_scope())->replace_this_binding(result);
auto prototype = new_target.get(names.prototype); auto prototype = new_target.get(names.prototype);
if (exception()) if (exception())
return {}; return {};
@ -292,22 +294,23 @@ String VM::join_arguments() const
Value VM::resolve_this_binding(GlobalObject& global_object) 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. // 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()) { for (auto* scope = current_scope(); scope; scope = scope->parent()) {
if (environment->has_this_binding()) if (scope->has_this_binding())
return environment; return scope;
} }
ASSERT_NOT_REACHED(); ASSERT_NOT_REACHED();
} }
Value VM::get_new_target() const Value VM::get_new_target() const
{ {
return get_this_environment()->new_target(); ASSERT(find_this_scope()->is_lexical_environment());
return static_cast<const LexicalEnvironment*>(find_this_scope())->new_target();
} }
Value VM::call_internal(Function& function, Value this_value, Optional<MarkedValueList> arguments) Value VM::call_internal(Function& function, Value this_value, Optional<MarkedValueList> arguments)
@ -321,10 +324,11 @@ Value VM::call_internal(Function& function, Value this_value, Optional<MarkedVal
call_frame.arguments = function.bound_arguments(); call_frame.arguments = function.bound_arguments();
if (arguments.has_value()) if (arguments.has_value())
call_frame.arguments.append(move(arguments.release_value().values())); call_frame.arguments.append(move(arguments.release_value().values()));
call_frame.environment = function.create_environment(); auto* environment = function.create_environment();
call_frame.scope = environment;
ASSERT(call_frame.environment->this_binding_status() == LexicalEnvironment::ThisBindingStatus::Uninitialized); ASSERT(environment->this_binding_status() == LexicalEnvironment::ThisBindingStatus::Uninitialized);
call_frame.environment->bind_this_value(function.global_object(), call_frame.this_value); environment->bind_this_value(function.global_object(), call_frame.this_value);
if (exception()) if (exception())
return {}; return {};

View file

@ -59,7 +59,7 @@ struct CallFrame {
FlyString function_name; FlyString function_name;
Value this_value; Value this_value;
Vector<Value> arguments; Vector<Value> arguments;
LexicalEnvironment* environment { nullptr }; ScopeObject* scope { nullptr };
bool is_strict_mode { false }; bool is_strict_mode { false };
}; };
@ -134,8 +134,8 @@ public:
const Vector<CallFrame*>& call_stack() const { return m_call_stack; } const Vector<CallFrame*>& call_stack() const { return m_call_stack; }
Vector<CallFrame*>& call_stack() { return m_call_stack; } Vector<CallFrame*>& call_stack() { return m_call_stack; }
const LexicalEnvironment* current_environment() const { return call_frame().environment; } const ScopeObject* current_scope() const { return call_frame().scope; }
LexicalEnvironment* current_environment() { return call_frame().environment; } ScopeObject* current_scope() { return call_frame().scope; }
bool in_strict_mode() const; bool in_strict_mode() const;
@ -222,7 +222,7 @@ public:
String join_arguments() const; String join_arguments() const;
Value resolve_this_binding(GlobalObject&) const; Value resolve_this_binding(GlobalObject&) const;
const LexicalEnvironment* get_this_environment() const; const ScopeObject* find_this_scope() const;
Value get_new_target() const; Value get_new_target() const;
template<typename... Args> template<typename... Args>
@ -242,6 +242,8 @@ public:
CommonPropertyNames names; CommonPropertyNames names;
Shape& scope_object_shape() { return *m_scope_object_shape; }
private: private:
VM(); VM();
@ -271,6 +273,8 @@ private:
Symbol* m_well_known_symbol_##snake_name { nullptr }; Symbol* m_well_known_symbol_##snake_name { nullptr };
JS_ENUMERATE_WELL_KNOWN_SYMBOLS JS_ENUMERATE_WELL_KNOWN_SYMBOLS
#undef __JS_ENUMERATE #undef __JS_ENUMERATE
Shape* m_scope_object_shape { nullptr };
}; };
template<> template<>