Selaa lähdekoodia

LibJS+Clients: Add JS::VM object, separate Heap from Interpreter

Taking a big step towards a world of multiple global object, this patch
adds a new JS::VM object that houses the JS::Heap.

This means that the Heap moves out of Interpreter, and the same Heap
can now be used by multiple Interpreters, and can also outlive them.

The VM keeps a stack of Interpreter pointers. We push/pop on this
stack when entering/exiting execution with a given Interpreter.
This allows us to make this change without disturbing too much of
the existing code.

There is still a 1-to-1 relationship between Interpreter and the
global object. This will change in the future.

Ultimately, the goal here is to make Interpreter a transient object
that only needs to exist while you execute some code. Getting there
will take a lot more work though. :^)

Note that in LibWeb, the global JS::VM is called main_thread_vm(),
to distinguish it from future worker VM's.
Andreas Kling 4 vuotta sitten
vanhempi
commit
1c43442be4

+ 9 - 1
Applications/Spreadsheet/Workbook.cpp

@@ -37,9 +37,17 @@
 
 namespace Spreadsheet {
 
+static JS::VM& global_vm()
+{
+    static RefPtr<JS::VM> vm;
+    if (!vm)
+        vm = JS::VM::create();
+    return *vm;
+}
+
 Workbook::Workbook(NonnullRefPtrVector<Sheet>&& sheets)
     : m_sheets(move(sheets))
-    , m_interpreter(JS::Interpreter::create<JS::GlobalObject>())
+    , m_interpreter(JS::Interpreter::create<JS::GlobalObject>(global_vm()))
 {
     m_workbook_object = interpreter().heap().allocate<WorkbookObject>(global_object(), *this);
     global_object().put("workbook", workbook_object());

+ 1 - 0
Libraries/LibJS/CMakeLists.txt

@@ -72,6 +72,7 @@ set(SOURCES
     Runtime/SymbolObject.cpp
     Runtime/SymbolPrototype.cpp
     Runtime/Uint8ClampedArray.cpp
+    Runtime/VM.cpp
     Runtime/Value.cpp
     Token.cpp
 )

+ 1 - 0
Libraries/LibJS/Forward.h

@@ -119,6 +119,7 @@ class Statement;
 class Symbol;
 class Token;
 class Uint8ClampedArray;
+class VM;
 class Value;
 enum class DeclarationKind;
 

+ 9 - 3
Libraries/LibJS/Heap/Heap.cpp

@@ -47,8 +47,8 @@
 
 namespace JS {
 
-Heap::Heap(Interpreter& interpreter)
-    : m_interpreter(interpreter)
+Heap::Heap(VM& vm)
+    : m_vm(vm)
 {
 }
 
@@ -57,6 +57,11 @@ Heap::~Heap()
     collect_garbage(CollectionType::CollectEverything);
 }
 
+Interpreter& Heap::interpreter()
+{
+    return vm().interpreter();
+}
+
 Cell* Heap::allocate_cell(size_t size)
 {
     if (should_collect_on_every_allocation()) {
@@ -100,7 +105,8 @@ void Heap::collect_garbage(CollectionType collection_type, bool print_report)
 
 void Heap::gather_roots(HashTable<Cell*>& roots)
 {
-    m_interpreter.gather_roots({}, roots);
+    if (auto* interpreter = vm().interpreter_if_exists())
+        interpreter->gather_roots({}, roots);
 
     gather_conservative_roots(roots);
 

+ 4 - 3
Libraries/LibJS/Heap/Heap.h

@@ -43,7 +43,7 @@ class Heap {
     AK_MAKE_NONMOVABLE(Heap);
 
 public:
-    explicit Heap(Interpreter&);
+    explicit Heap(VM&);
     ~Heap();
 
     template<typename T, typename... Args>
@@ -71,7 +71,8 @@ public:
 
     void collect_garbage(CollectionType = CollectionType::CollectGarbage, bool print_report = false);
 
-    Interpreter& interpreter() { return m_interpreter; }
+    Interpreter& interpreter();
+    VM& vm() { return m_vm; }
 
     bool should_collect_on_every_allocation() const { return m_should_collect_on_every_allocation; }
     void set_should_collect_on_every_allocation(bool b) { m_should_collect_on_every_allocation = b; }
@@ -100,7 +101,7 @@ private:
 
     bool m_should_collect_on_every_allocation { false };
 
-    Interpreter& m_interpreter;
+    VM& m_vm;
     Vector<NonnullOwnPtr<HeapBlock>> m_blocks;
     HashTable<HandleImpl*> m_handles;
 

+ 8 - 5
Libraries/LibJS/Interpreter.cpp

@@ -44,8 +44,8 @@
 
 namespace JS {
 
-Interpreter::Interpreter()
-    : m_heap(*this)
+Interpreter::Interpreter(VM& vm)
+    : m_vm(vm)
     , m_console(*this)
 {
 #define __JS_ENUMERATE(SymbolName, snake_name) \
@@ -60,6 +60,8 @@ Interpreter::~Interpreter()
 
 Value Interpreter::run(GlobalObject& global_object, const Program& program)
 {
+    VM::InterpreterScope scope(*this);
+
     ASSERT(!exception());
 
     if (m_call_stack.is_empty()) {
@@ -223,7 +225,6 @@ Symbol* Interpreter::get_global_symbol(const String& description)
 
 void Interpreter::gather_roots(Badge<Heap>, HashTable<Cell*>& roots)
 {
-    roots.set(m_global_object);
     roots.set(m_exception);
 
     if (m_last_value.is_cell())
@@ -252,6 +253,8 @@ Value Interpreter::call_internal(Function& function, Value this_value, Optional<
 {
     ASSERT(!exception());
 
+    VM::InterpreterScope scope(*this);
+
     auto& call_frame = push_call_frame();
     call_frame.function_name = function.name();
     call_frame.this_value = function.bound_this().value_or(this_value);
@@ -348,12 +351,12 @@ void Interpreter::throw_exception(Exception* exception)
 
 GlobalObject& Interpreter::global_object()
 {
-    return static_cast<GlobalObject&>(*m_global_object);
+    return static_cast<GlobalObject&>(*m_global_object.cell());
 }
 
 const GlobalObject& Interpreter::global_object() const
 {
-    return static_cast<const GlobalObject&>(*m_global_object);
+    return static_cast<const GlobalObject&>(*m_global_object.cell());
 }
 
 String Interpreter::join_arguments() const

+ 13 - 8
Libraries/LibJS/Interpreter.h

@@ -34,11 +34,13 @@
 #include <LibJS/AST.h>
 #include <LibJS/Console.h>
 #include <LibJS/Forward.h>
+#include <LibJS/Heap/DeferGC.h>
 #include <LibJS/Heap/Heap.h>
 #include <LibJS/Runtime/ErrorTypes.h>
 #include <LibJS/Runtime/Exception.h>
 #include <LibJS/Runtime/LexicalEnvironment.h>
 #include <LibJS/Runtime/MarkedValueList.h>
+#include <LibJS/Runtime/VM.h>
 #include <LibJS/Runtime/Value.h>
 
 namespace JS {
@@ -75,11 +77,13 @@ typedef Vector<Argument, 8> ArgumentVector;
 class Interpreter : public Weakable<Interpreter> {
 public:
     template<typename GlobalObjectType, typename... Args>
-    static NonnullOwnPtr<Interpreter> create(Args&&... args)
+    static NonnullOwnPtr<Interpreter> create(VM& vm, Args&&... args)
     {
-        auto interpreter = adopt_own(*new Interpreter);
-        interpreter->m_global_object = interpreter->heap().allocate_without_global_object<GlobalObjectType>(forward<Args>(args)...);
-        static_cast<GlobalObjectType*>(interpreter->m_global_object)->initialize();
+        DeferGC defer_gc(vm.heap());
+        auto interpreter = adopt_own(*new Interpreter(vm));
+        VM::InterpreterScope scope(*interpreter);
+        interpreter->m_global_object = make_handle(static_cast<Object*>(interpreter->heap().allocate_without_global_object<GlobalObjectType>(forward<Args>(args)...)));
+        static_cast<GlobalObjectType*>(interpreter->m_global_object.cell())->initialize();
         return interpreter;
     }
 
@@ -107,7 +111,8 @@ public:
     GlobalObject& global_object();
     const GlobalObject& global_object() const;
 
-    Heap& heap() { return m_heap; }
+    VM& vm() { return *m_vm; }
+    Heap& heap() { return vm().heap(); }
 
     void unwind(ScopeType type, FlyString label = {})
     {
@@ -234,18 +239,18 @@ public:
 #undef __JS_ENUMERATE
 
 private:
-    Interpreter();
+    explicit Interpreter(VM&);
 
     [[nodiscard]] Value call_internal(Function&, Value this_value, Optional<MarkedValueList>);
 
-    Heap m_heap;
+    NonnullRefPtr<VM> m_vm;
 
     Value m_last_value;
 
     Vector<ScopeFrame> m_scope_stack;
     Vector<CallFrame> m_call_stack;
 
-    Object* m_global_object { nullptr };
+    Handle<Object> m_global_object;
 
     Exception* m_exception { nullptr };
 

+ 3 - 3
Libraries/LibJS/Runtime/GlobalObject.cpp

@@ -26,6 +26,7 @@
  */
 
 #include <AK/LogStream.h>
+#include <LibJS/Heap/DeferGC.h>
 #include <LibJS/Interpreter.h>
 #include <LibJS/Runtime/ArrayConstructor.h>
 #include <LibJS/Runtime/ArrayIteratorPrototype.h>
@@ -87,13 +88,12 @@ void GlobalObject::initialize()
     JS_ENUMERATE_BUILTIN_TYPES
 #undef __JS_ENUMERATE
 
-#define __JS_ENUMERATE(ClassName, snake_name)                                    \
-    if (!m_##snake_name##_prototype)                                             \
+#define __JS_ENUMERATE(ClassName, snake_name) \
+    if (!m_##snake_name##_prototype)          \
         m_##snake_name##_prototype = heap().allocate<ClassName##Prototype>(*this, *this);
     JS_ENUMERATE_ITERATOR_PROTOTYPES
 #undef __JS_ENUMERATE
 
-
     u8 attr = Attribute::Writable | Attribute::Configurable;
     define_native_function("gc", gc, 0, attr);
     define_native_function("isNaN", is_nan, 1, attr);

+ 85 - 0
Libraries/LibJS/Runtime/VM.cpp

@@ -0,0 +1,85 @@
+/*
+ * 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/Interpreter.h>
+#include <LibJS/Runtime/VM.h>
+
+namespace JS {
+
+NonnullRefPtr<VM> VM::create()
+{
+    return adopt(*new VM);
+}
+
+VM::VM()
+    : m_heap(*this)
+{
+}
+
+VM::~VM()
+{
+}
+
+Interpreter& VM::interpreter()
+{
+    if (m_interpreters.is_empty()) {
+        asm volatile("ud2");
+    }
+//    ASSERT(!m_interpreters.is_empty());
+    return *m_interpreters.last();
+}
+
+Interpreter* VM::interpreter_if_exists()
+{
+    if (m_interpreters.is_empty())
+        return nullptr;
+    return m_interpreters.last();
+}
+
+void VM::push_interpreter(Interpreter& interpreter)
+{
+    m_interpreters.append(&interpreter);
+}
+
+void VM::pop_interpreter(Interpreter& interpreter)
+{
+    ASSERT(!m_interpreters.is_empty());
+    auto* popped_interpreter = m_interpreters.take_last();
+    ASSERT(popped_interpreter == &interpreter);
+}
+
+VM::InterpreterScope::InterpreterScope(Interpreter& interpreter)
+    : m_interpreter(interpreter)
+{
+    m_interpreter.vm().push_interpreter(m_interpreter);
+}
+
+VM::InterpreterScope::~InterpreterScope()
+{
+    m_interpreter.vm().pop_interpreter(m_interpreter);
+}
+
+}

+ 63 - 0
Libraries/LibJS/Runtime/VM.h

@@ -0,0 +1,63 @@
+/*
+ * 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 <AK/RefCounted.h>
+#include <LibJS/Heap/Heap.h>
+
+namespace JS {
+
+class VM : public RefCounted<VM> {
+public:
+    static NonnullRefPtr<VM> create();
+    ~VM();
+
+    Heap& heap() { return m_heap; }
+    const Heap& heap() const { return m_heap; }
+
+    Interpreter& interpreter();
+    Interpreter* interpreter_if_exists();
+
+    void push_interpreter(Interpreter&);
+    void pop_interpreter(Interpreter&);
+
+    class InterpreterScope {
+    public:
+        InterpreterScope(Interpreter&);
+        ~InterpreterScope();
+    private:
+        Interpreter& m_interpreter;
+    };
+
+private:
+    VM();
+
+    Heap m_heap;
+    Vector<Interpreter*> m_interpreters;
+};
+
+}

+ 9 - 1
Libraries/LibWeb/DOM/Document.cpp

@@ -408,10 +408,18 @@ Color Document::visited_link_color() const
     return frame()->page().palette().visited_link();
 }
 
+static JS::VM& main_thread_vm()
+{
+    static RefPtr<JS::VM> vm;
+    if (!vm)
+        vm = JS::VM::create();
+    return *vm;
+}
+
 JS::Interpreter& Document::interpreter()
 {
     if (!m_interpreter)
-        m_interpreter = JS::Interpreter::create<Bindings::WindowObject>(*m_window);
+        m_interpreter = JS::Interpreter::create<Bindings::WindowObject>(main_thread_vm(), *m_window);
     return *m_interpreter;
 }
 

+ 3 - 2
Userland/js.cpp

@@ -552,6 +552,7 @@ int main(int argc, char** argv)
 
     bool syntax_highlight = !disable_syntax_highlight;
 
+    auto vm = JS::VM::create();
     OwnPtr<JS::Interpreter> interpreter;
 
     interrupt_interpreter = [&] {
@@ -561,7 +562,7 @@ int main(int argc, char** argv)
 
     if (script_path == nullptr) {
         s_print_last_result = true;
-        interpreter = JS::Interpreter::create<ReplObject>();
+        interpreter = JS::Interpreter::create<ReplObject>(*vm);
         ReplConsoleClient console_client(interpreter->console());
         interpreter->console().set_client(console_client);
         interpreter->heap().set_should_collect_on_every_allocation(gc_on_every_allocation);
@@ -842,7 +843,7 @@ int main(int argc, char** argv)
         s_editor->on_tab_complete = move(complete);
         repl(*interpreter);
     } else {
-        interpreter = JS::Interpreter::create<JS::GlobalObject>();
+        interpreter = JS::Interpreter::create<JS::GlobalObject>(*vm);
         ReplConsoleClient console_client(interpreter->console());
         interpreter->console().set_client(console_client);
         interpreter->heap().set_should_collect_on_every_allocation(gc_on_every_allocation);

+ 10 - 1
Userland/test-js.cpp

@@ -43,6 +43,8 @@
 
 #define TOP_LEVEL_TEST_NAME "__$$TOP_LEVEL$$__"
 
+RefPtr<JS::VM> vm;
+
 static bool collect_on_every_allocation = false;
 static String currently_running_test;
 
@@ -273,7 +275,10 @@ JSFileResult TestRunner::run_file_test(const String& test_path)
     currently_running_test = test_path;
 
     double start_time = get_time_in_ms();
-    auto interpreter = JS::Interpreter::create<TestRunnerGlobalObject>();
+    auto interpreter = JS::Interpreter::create<TestRunnerGlobalObject>(*vm);
+
+    // FIXME: This is a hack while we're refactoring Interpreter/VM stuff.
+    JS::VM::InterpreterScope scope(*interpreter);
 
     interpreter->heap().set_should_collect_on_every_allocation(collect_on_every_allocation);
 
@@ -603,6 +608,8 @@ int main(int argc, char** argv)
         DebugLogStream::set_enabled(false);
     }
 
+    vm = JS::VM::create();
+
 #ifdef __serenity__
     TestRunner("/home/anon/js-tests", print_times).run();
 #else
@@ -614,5 +621,7 @@ int main(int argc, char** argv)
     TestRunner(String::format("%s/Libraries/LibJS/Tests", serenity_root), print_times).run();
 #endif
 
+    vm = nullptr;
+
     return 0;
 }