diff --git a/Libraries/LibJS/Forward.h b/Libraries/LibJS/Forward.h index d5df55ba34b..bdd8c1fd1b6 100644 --- a/Libraries/LibJS/Forward.h +++ b/Libraries/LibJS/Forward.h @@ -43,6 +43,7 @@ class Interpreter; class Object; class PrimitiveString; class ScopeNode; +class Shape; class Statement; class Value; enum class DeclarationType; diff --git a/Libraries/LibJS/Interpreter.cpp b/Libraries/LibJS/Interpreter.cpp index 1c96f23b7f3..6350fdd0ae0 100644 --- a/Libraries/LibJS/Interpreter.cpp +++ b/Libraries/LibJS/Interpreter.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -43,6 +44,8 @@ namespace JS { Interpreter::Interpreter() : m_heap(*this) { + m_empty_object_shape = heap().allocate(); + m_object_prototype = heap().allocate(); m_string_prototype = heap().allocate(); m_array_prototype = heap().allocate(); @@ -160,6 +163,8 @@ Optional Interpreter::get_variable(const FlyString& name) void Interpreter::gather_roots(Badge, HashTable& roots) { + roots.set(m_empty_object_shape); + roots.set(m_global_object); roots.set(m_string_prototype); roots.set(m_object_prototype); diff --git a/Libraries/LibJS/Interpreter.h b/Libraries/LibJS/Interpreter.h index 77a4f7622cc..ab589e8e3cd 100644 --- a/Libraries/LibJS/Interpreter.h +++ b/Libraries/LibJS/Interpreter.h @@ -129,6 +129,8 @@ public: return m_call_stack.last().this_value; } + Shape* empty_object_shape() { return m_empty_object_shape; } + Object* string_prototype() { return m_string_prototype; } Object* object_prototype() { return m_object_prototype; } Object* array_prototype() { return m_array_prototype; } @@ -158,6 +160,8 @@ private: Vector m_scope_stack; Vector m_call_stack; + Shape* m_empty_object_shape { nullptr }; + Object* m_global_object { nullptr }; Object* m_string_prototype { nullptr }; Object* m_object_prototype { nullptr }; diff --git a/Libraries/LibJS/Makefile b/Libraries/LibJS/Makefile index 624f2dd5918..1a18967fc15 100644 --- a/Libraries/LibJS/Makefile +++ b/Libraries/LibJS/Makefile @@ -27,6 +27,7 @@ OBJS = \ Runtime/ObjectPrototype.o \ Runtime/PrimitiveString.o \ Runtime/ScriptFunction.o \ + Runtime/Shape.o \ Runtime/StringObject.o \ Runtime/StringPrototype.o \ Runtime/Value.o \ diff --git a/Libraries/LibJS/Runtime/Object.cpp b/Libraries/LibJS/Runtime/Object.cpp index ef503ea833e..4cf7b872097 100644 --- a/Libraries/LibJS/Runtime/Object.cpp +++ b/Libraries/LibJS/Runtime/Object.cpp @@ -31,22 +31,39 @@ #include #include #include +#include #include namespace JS { Object::Object() { - set_prototype(interpreter().object_prototype()); + m_shape = interpreter().empty_object_shape(); + m_shape->set_prototype_without_transition(interpreter().object_prototype()); } Object::~Object() { } +Object* Object::prototype() +{ + return shape().prototype(); +} + +const Object* Object::prototype() const +{ + return shape().prototype(); +} + +void Object::set_prototype(Object* new_prototype) +{ + m_shape = m_shape->create_prototype_transition(new_prototype); +} + bool Object::has_prototype(const Object* prototype) const { - for (auto* object = m_prototype; object; object = object->prototype()) { + for (auto* object = this->prototype(); object; object = object->prototype()) { if (object == prototype) return true; } @@ -55,9 +72,13 @@ bool Object::has_prototype(const Object* prototype) const Optional Object::get_own_property(const Object& this_object, const FlyString& property_name) const { - auto value_here = m_properties.get(property_name); - if (value_here.has_value() && value_here.value().is_object() && value_here.value().as_object().is_native_property()) { - auto& native_property = static_cast(value_here.value().as_object()); + auto metadata = shape().lookup(property_name); + if (!metadata.has_value()) + return {}; + + auto value_here = m_storage[metadata.value().offset]; + if (value_here.is_object() && value_here.as_object().is_native_property()) { + auto& native_property = static_cast(value_here.as_object()); auto& interpreter = const_cast(this)->interpreter(); auto& call_frame = interpreter.push_call_frame(); call_frame.this_value = const_cast(&this_object); @@ -68,19 +89,32 @@ Optional Object::get_own_property(const Object& this_object, const FlyStr return value_here; } +void Object::set_shape(Shape& new_shape) +{ + m_storage.resize(new_shape.property_count()); + m_shape = &new_shape; +} + bool Object::put_own_property(Object& this_object, const FlyString& property_name, Value value) { - auto value_here = m_properties.get(property_name); - if (value_here.has_value() && value_here.value().is_object() && value_here.value().as_object().is_native_property()) { - auto& native_property = static_cast(value_here.value().as_object()); + auto metadata = shape().lookup(property_name); + if (!metadata.has_value()) { + auto* new_shape = m_shape->create_put_transition(property_name, 0); + set_shape(*new_shape); + metadata = shape().lookup(property_name); + ASSERT(metadata.has_value()); + } + + auto value_here = m_storage[metadata.value().offset]; + if (value_here.is_object() && value_here.as_object().is_native_property()) { + auto& native_property = static_cast(value_here.as_object()); auto& interpreter = const_cast(this)->interpreter(); auto& call_frame = interpreter.push_call_frame(); call_frame.this_value = &this_object; - dbg() << "put_own_property: " << &this_object << " . " << property_name << " = " << value; native_property.set(interpreter, value); interpreter.pop_call_frame(); } else { - m_properties.set(property_name, value); + m_storage[metadata.value().offset] = value; } return true; } @@ -101,10 +135,11 @@ void Object::put(const FlyString& property_name, Value value) { Object* object = this; while (object) { - auto value_here = object->m_properties.get(property_name); - if (value_here.has_value()) { - if (value_here.value().is_object() && value_here.value().as_object().is_native_property()) { - auto& native_property = static_cast(value_here.value().as_object()); + auto metadata = object->shape().lookup(property_name); + if (metadata.has_value()) { + auto value_here = object->m_storage[metadata.value().offset]; + if (value_here.is_object() && value_here.as_object().is_native_property()) { + auto& native_property = static_cast(value_here.as_object()); auto& interpreter = const_cast(this)->interpreter(); auto& call_frame = interpreter.push_call_frame(); call_frame.this_value = this; @@ -133,15 +168,12 @@ void Object::put_native_property(const FlyString& property_name, AK::Function get(const FlyString& property_name) const; void put(const FlyString& property_name, Value); @@ -60,9 +63,9 @@ public: virtual const char* class_name() const override { return "Object"; } virtual void visit_children(Cell::Visitor&) override; - Object* prototype() { return m_prototype; } - const Object* prototype() const { return m_prototype; } - void set_prototype(Object* prototype) { m_prototype = prototype; } + Object* prototype(); + const Object* prototype() const; + void set_prototype(Object*); bool has_prototype(const Object* prototype) const; bool has_own_property(const FlyString& property_name) const; @@ -76,11 +79,13 @@ public: virtual Value to_primitive(PreferredType preferred_type = PreferredType::Default) const; virtual Value to_string() const; - const HashMap& own_properties() const { return m_properties; } + Value get_direct(size_t index) const { return m_storage[index]; } private: - HashMap m_properties; - Object* m_prototype { nullptr }; + void set_shape(Shape&); + + Shape* m_shape { nullptr }; + Vector m_storage; }; } diff --git a/Libraries/LibJS/Runtime/ObjectConstructor.cpp b/Libraries/LibJS/Runtime/ObjectConstructor.cpp index ee529724cb6..4b102a3829b 100644 --- a/Libraries/LibJS/Runtime/ObjectConstructor.cpp +++ b/Libraries/LibJS/Runtime/ObjectConstructor.cpp @@ -29,6 +29,7 @@ #include #include #include +#include namespace JS { @@ -68,7 +69,7 @@ Value ObjectConstructor::get_own_property_names(Interpreter& interpreter) for (i32 i = 0; i < array->length(); ++i) result->push(js_string(interpreter.heap(), String::number(i))); } - for (auto& it : object->own_properties()) + for (auto& it : object->shape().property_table()) result->push(js_string(interpreter.heap(), it.key)); return result; } diff --git a/Libraries/LibJS/Runtime/Shape.cpp b/Libraries/LibJS/Runtime/Shape.cpp new file mode 100644 index 00000000000..3f89ff99fe6 --- /dev/null +++ b/Libraries/LibJS/Runtime/Shape.cpp @@ -0,0 +1,95 @@ +#include +#include + +namespace JS { + +Shape* Shape::create_put_transition(const FlyString& property_name, u8 property_attributes) +{ + auto* new_shape = m_forward_transitions.get(property_name).value_or(nullptr); + if (new_shape && new_shape->m_property_attributes == property_attributes) + return new_shape; + new_shape = heap().allocate(this, property_name, property_attributes); + m_forward_transitions.set(property_name, new_shape); + return new_shape; +} + +Shape* Shape::create_prototype_transition(Object* new_prototype) +{ + return heap().allocate(this, new_prototype); +} + +Shape::Shape() +{ +} + +Shape::Shape(Shape* previous_shape, const FlyString& property_name, u8 property_attributes) + : m_previous(previous_shape) + , m_property_name(property_name) + , m_property_attributes(property_attributes) + , m_prototype(previous_shape->m_prototype) +{ +} + +Shape::Shape(Shape* previous_shape, Object* new_prototype) + : m_previous(previous_shape) + , m_prototype(new_prototype) +{ +} + +Shape::~Shape() +{ +} + +void Shape::visit_children(Cell::Visitor& visitor) +{ + Cell::visit_children(visitor); + if (m_prototype) + visitor.visit(m_prototype); + if (m_previous) + visitor.visit(m_previous); + for (auto& it : m_forward_transitions) + visitor.visit(it.value); +} + +Optional Shape::lookup(const FlyString& property_name) const +{ + return property_table().get(property_name); +} + +const HashMap& Shape::property_table() const +{ + ensure_property_table(); + return *m_property_table; +} + +size_t Shape::property_count() const +{ + return property_table().size(); +} + +void Shape::ensure_property_table() const +{ + if (m_property_table) + return; + m_property_table = make>(); + + // FIXME: We need to make sure the GC doesn't collect the transition chain as we're building it. + // Maybe some kind of RAII "prevent GC for a moment" helper thingy? + + Vector transition_chain; + for (auto* shape = this; shape->m_previous; shape = shape->m_previous) { + transition_chain.append(shape); + } + + u32 next_offset = 0; + for (ssize_t i = transition_chain.size() - 1; i >= 0; --i) { + auto* shape = transition_chain[i]; + if (shape->m_property_name.is_null()) { + // Ignore prototype transitions as they don't affect the key map. + continue; + } + m_property_table->set(shape->m_property_name, { next_offset++, shape->m_property_attributes }); + } +} + +} diff --git a/Libraries/LibJS/Runtime/Shape.h b/Libraries/LibJS/Runtime/Shape.h new file mode 100644 index 00000000000..66613160ecf --- /dev/null +++ b/Libraries/LibJS/Runtime/Shape.h @@ -0,0 +1,78 @@ +/* + * 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 +#include +#include +#include +#include +#include + +namespace JS { + +struct PropertyMetadata { + size_t offset { 0 }; + u8 attributes { 0 }; +}; + +class Shape final : public Cell { +public: + virtual ~Shape() override; + + Shape(); + Shape(Shape* previous_shape, const FlyString& property_name, u8 property_attributes); + Shape(Shape* previous_shape, Object* new_prototype); + + Shape* create_put_transition(const FlyString& name, u8 attributes); + Shape* create_prototype_transition(Object* new_prototype); + + Object* prototype() { return m_prototype; } + const Object* prototype() const { return m_prototype; } + + Optional lookup(const FlyString&) const; + const HashMap& property_table() const; + size_t property_count() const; + + void set_prototype_without_transition(Object* new_prototype) { m_prototype = new_prototype; } + +private: + virtual const char* class_name() const override { return "Shape"; } + virtual void visit_children(Visitor&) override; + + void ensure_property_table() const; + + mutable OwnPtr> m_property_table; + + HashMap m_forward_transitions; + Shape* m_previous { nullptr }; + FlyString m_property_name; + u8 m_property_attributes { 0 }; + Object* m_prototype { nullptr }; +}; + +} diff --git a/Userland/js.cpp b/Userland/js.cpp index f64ca74cf43..951f866041d 100644 --- a/Userland/js.cpp +++ b/Userland/js.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -113,10 +114,10 @@ static void print_object(const JS::Object& object, HashTable& seen_ { fputs("{ ", stdout); size_t index = 0; - for (auto& it : object.own_properties()) { + for (auto& it : object.shape().property_table()) { printf("\"\033[33;1m%s\033[0m\": ", it.key.characters()); - print_value(it.value, seen_objects); - if (index != object.own_properties().size() - 1) + print_value(object.get_direct(it.value.offset), seen_objects); + if (index != object.shape().property_table().size() - 1) fputs(", ", stdout); ++index; }