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.
This commit is contained in:
Andreas Kling 2020-09-20 19:24:44 +02:00
parent c6ae0c41d9
commit 1c43442be4
Notes: sideshowbarker 2024-07-19 02:19:23 +09:00
13 changed files with 218 additions and 27 deletions

View file

@ -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());

View file

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

View file

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

View file

@ -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);

View file

@ -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;

View file

@ -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

View file

@ -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 };

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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;
};
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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;
}