Skip to content

Commit

Permalink
LibJS/Bytecode: Simplify Bytecode::Interpreter lifetime model
Browse files Browse the repository at this point in the history
The JS::VM now owns the one Bytecode::Interpreter. We no longer have
multiple bytecode interpreters, and there is no concept of a "current"
bytecode interpreter.

If you ask for VM::bytecode_interpreter_if_exists(), it will return null
if we're not running the program in "bytecode enabled" mode.

If you ask for VM::bytecode_interpreter(), it will return a bytecode
interpreter in all modes. This is used for situations where even the AST
interpreter switches to bytecode mode (generators, etc.)
  • Loading branch information
awesomekling committed Jun 22, 2023
1 parent 66c1c74 commit a4241fd
Show file tree
Hide file tree
Showing 14 changed files with 115 additions and 99 deletions.
6 changes: 2 additions & 4 deletions Tests/LibJS/test262-runner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -215,12 +215,10 @@ static Result<void, TestError> run_test(StringView source, StringView filepath,
if (program_or_error.is_error())
return program_or_error.release_error();

OwnPtr<JS::Bytecode::Interpreter> bytecode_interpreter = nullptr;
if (JS::Bytecode::Interpreter::enabled())
bytecode_interpreter = make<JS::Bytecode::Interpreter>(realm);
auto* bytecode_interpreter = vm->bytecode_interpreter_if_exists();

auto run_with_interpreter = [&](ScriptOrModuleProgram& program) {
if (JS::Bytecode::Interpreter::enabled())
if (bytecode_interpreter)
return run_program(*bytecode_interpreter, program);
return run_program(*ast_interpreter, program);
};
Expand Down
56 changes: 35 additions & 21 deletions Userland/Libraries/LibJS/Bytecode/Interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,15 @@ void Interpreter::set_enabled(bool enabled)
s_bytecode_interpreter_enabled = enabled;
}

static Interpreter* s_current;
bool g_dump_bytecode = false;

Interpreter* Interpreter::current()
Interpreter::Interpreter(VM& vm)
: m_vm(vm)
{
return s_current;
}

Interpreter::Interpreter(Realm& realm)
: m_vm(realm.vm())
, m_realm(realm)
{
VERIFY(!s_current);
s_current = this;
}

Interpreter::~Interpreter()
{
VERIFY(s_current == this);
s_current = nullptr;
}

// 16.1.6 ScriptEvaluation ( scriptRecord ), https://tc39.es/ecma262/#sec-runtime-semantics-scriptevaluation
Expand Down Expand Up @@ -124,7 +113,7 @@ ThrowCompletionOr<Value> Interpreter::run(Script& script_record, JS::GCPtr<Envir
executable->dump();

// a. Set result to the result of evaluating script.
auto result_or_error = run_and_return_frame(*executable, nullptr);
auto result_or_error = run_and_return_frame(script_record.realm(), *executable, nullptr);
if (result_or_error.value.is_error())
result = result_or_error.value.release_error();
else
Expand Down Expand Up @@ -189,7 +178,7 @@ void Interpreter::set_optimizations_enabled(bool enabled)
m_optimizations_enabled = enabled;
}

Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable const& executable, BasicBlock const* entry_point, RegisterWindow* in_frame)
Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Realm& realm, Executable const& executable, BasicBlock const* entry_point, RegisterWindow* in_frame)
{
dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter will run unit {:p}", &executable);

Expand All @@ -201,12 +190,12 @@ Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable const& e
ExecutionContext execution_context(vm().heap());
if (vm().execution_context_stack().is_empty() || !vm().running_execution_context().lexical_environment) {
// The "normal" interpreter pushes an execution context without environment so in that case we also want to push one.
execution_context.this_value = &m_realm->global_object();
execution_context.this_value = &realm.global_object();
static DeprecatedFlyString global_execution_context_name = "(*BC* global execution context)";
execution_context.function_name = global_execution_context_name;
execution_context.lexical_environment = &m_realm->global_environment();
execution_context.variable_environment = &m_realm->global_environment();
execution_context.realm = m_realm;
execution_context.lexical_environment = &realm.global_environment();
execution_context.variable_environment = &realm.global_environment();
execution_context.realm = realm;
execution_context.is_strict_mode = executable.is_strict_mode;
vm().push_execution_context(execution_context);
pushed_execution_context = true;
Expand Down Expand Up @@ -395,10 +384,10 @@ ThrowCompletionOr<void> Interpreter::continue_pending_unwind(Label const& resume
return {};
}

VM::InterpreterExecutionScope Interpreter::ast_interpreter_scope()
VM::InterpreterExecutionScope Interpreter::ast_interpreter_scope(Realm& realm)
{
if (!m_ast_interpreter)
m_ast_interpreter = JS::Interpreter::create_with_existing_realm(m_realm);
m_ast_interpreter = JS::Interpreter::create_with_existing_realm(realm);

return { *m_ast_interpreter };
}
Expand Down Expand Up @@ -449,4 +438,29 @@ DeprecatedString Interpreter::debug_position() const
return DeprecatedString::formatted("{}:{:2}:{:4x}", m_current_executable->name, m_current_block->name(), pc());
}

ThrowCompletionOr<NonnullOwnPtr<Bytecode::Executable>> compile(VM& vm, ASTNode const& node, FunctionKind kind, DeprecatedFlyString const& name)
{
auto executable_result = Bytecode::Generator::generate(node, kind);
if (executable_result.is_error())
return vm.throw_completion<InternalError>(ErrorType::NotImplemented, TRY_OR_THROW_OOM(vm, executable_result.error().to_string()));

auto bytecode_executable = executable_result.release_value();
bytecode_executable->name = name;
auto& passes = Bytecode::Interpreter::optimization_pipeline();
passes.perform(*bytecode_executable);
if constexpr (JS_BYTECODE_DEBUG) {
dbgln("Optimisation passes took {}us", passes.elapsed());
dbgln("Compiled Bytecode::Block for function '{}':", name);
}
if (Bytecode::g_dump_bytecode)
bytecode_executable->dump();

return bytecode_executable;
}

Realm& Interpreter::realm()
{
return *m_vm.current_realm();
}

}
16 changes: 9 additions & 7 deletions Userland/Libraries/LibJS/Bytecode/Interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <LibJS/Forward.h>
#include <LibJS/Heap/Cell.h>
#include <LibJS/Heap/Handle.h>
#include <LibJS/Runtime/FunctionKind.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/Value.h>

Expand All @@ -31,31 +32,31 @@ class Interpreter {
[[nodiscard]] static bool enabled();
static void set_enabled(bool);

explicit Interpreter(Realm&);
explicit Interpreter(VM&);
~Interpreter();

// FIXME: Remove this thing once we don't need it anymore!
static Interpreter* current();

Realm& realm() { return m_realm; }
Realm& realm();
VM& vm() { return m_vm; }

void set_optimizations_enabled(bool);

ThrowCompletionOr<Value> run(Script&, JS::GCPtr<Environment> lexical_environment_override = nullptr);
ThrowCompletionOr<Value> run(SourceTextModule&);

ThrowCompletionOr<Value> run(Bytecode::Executable const& executable, Bytecode::BasicBlock const* entry_point = nullptr)
ThrowCompletionOr<Value> run(Realm& realm, Bytecode::Executable const& executable, Bytecode::BasicBlock const* entry_point = nullptr)
{
auto value_and_frame = run_and_return_frame(executable, entry_point);
auto value_and_frame = run_and_return_frame(realm, executable, entry_point);
return move(value_and_frame.value);
}

struct ValueAndFrame {
ThrowCompletionOr<Value> value;
OwnPtr<RegisterWindow> frame;
};
ValueAndFrame run_and_return_frame(Bytecode::Executable const&, Bytecode::BasicBlock const* entry_point, RegisterWindow* = nullptr);
ValueAndFrame run_and_return_frame(Realm&, Bytecode::Executable const&, Bytecode::BasicBlock const* entry_point, RegisterWindow* = nullptr);

ALWAYS_INLINE Value& accumulator() { return reg(Register::accumulator()); }
Value& reg(Register const& r) { return registers()[r.index()]; }
Expand Down Expand Up @@ -97,7 +98,7 @@ class Interpreter {
};
static Bytecode::PassManager& optimization_pipeline(OptimizationLevel = OptimizationLevel::Default);

VM::InterpreterExecutionScope ast_interpreter_scope();
VM::InterpreterExecutionScope ast_interpreter_scope(Realm&);

private:
RegisterWindow& window()
Expand All @@ -115,7 +116,6 @@ class Interpreter {
static AK::Array<OwnPtr<PassManager>, static_cast<UnderlyingType<Interpreter::OptimizationLevel>>(Interpreter::OptimizationLevel::__Count)> s_optimization_pipelines;

VM& m_vm;
NonnullGCPtr<Realm> m_realm;
Vector<Variant<NonnullOwnPtr<RegisterWindow>, RegisterWindow*>> m_register_windows;
Optional<BasicBlock const*> m_pending_jump;
BasicBlock const* m_scheduled_jump { nullptr };
Expand All @@ -131,4 +131,6 @@ class Interpreter {

extern bool g_dump_bytecode;

ThrowCompletionOr<NonnullOwnPtr<Bytecode::Executable>> compile(VM&, ASTNode const& no, JS::FunctionKind kind, DeprecatedFlyString const& name);

}
2 changes: 1 addition & 1 deletion Userland/Libraries/LibJS/Bytecode/Op.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1081,7 +1081,7 @@ ThrowCompletionOr<void> IteratorResultValue::execute_impl(Bytecode::Interpreter&
ThrowCompletionOr<void> NewClass::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto name = m_class_expression.name();
auto scope = interpreter.ast_interpreter_scope();
auto scope = interpreter.ast_interpreter_scope(interpreter.realm());
auto& ast_interpreter = scope.interpreter();

auto* class_object = TRY(m_class_expression.class_definition_evaluation(ast_interpreter, name, name.is_null() ? ""sv : name));
Expand Down
4 changes: 2 additions & 2 deletions Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ ThrowCompletionOr<Value> perform_eval(VM& vm, Value x, CallerMode strict_caller,

// 29. If result.[[Type]] is normal, then
// a. Set result to the result of evaluating body.
if (auto* bytecode_interpreter = Bytecode::Interpreter::current()) {
if (auto* bytecode_interpreter = vm.bytecode_interpreter_if_exists()) {
auto executable_result = Bytecode::Generator::generate(program);
if (executable_result.is_error())
return vm.throw_completion<InternalError>(ErrorType::NotImplemented, TRY_OR_THROW_OOM(vm, executable_result.error().to_string()));
Expand All @@ -710,7 +710,7 @@ ThrowCompletionOr<Value> perform_eval(VM& vm, Value x, CallerMode strict_caller,
executable->name = "eval"sv;
if (Bytecode::g_dump_bytecode)
executable->dump();
auto result_or_error = bytecode_interpreter->run_and_return_frame(*executable, nullptr);
auto result_or_error = bytecode_interpreter->run_and_return_frame(eval_realm, *executable, nullptr);
if (result_or_error.value.is_error())
return result_or_error.value.release_error();

Expand Down
49 changes: 19 additions & 30 deletions Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -455,8 +455,8 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
} else if (i < execution_context_arguments.size() && !execution_context_arguments[i].is_undefined()) {
argument_value = execution_context_arguments[i];
} else if (parameter.default_value) {
if (auto* bytecode_interpreter = Bytecode::Interpreter::current()) {
auto value_and_frame = bytecode_interpreter->run_and_return_frame(*m_default_parameter_bytecode_executables[default_parameter_index - 1], nullptr);
if (auto* bytecode_interpreter = vm.bytecode_interpreter_if_exists()) {
auto value_and_frame = bytecode_interpreter->run_and_return_frame(realm, *m_default_parameter_bytecode_executables[default_parameter_index - 1], nullptr);
if (value_and_frame.value.is_error())
return value_and_frame.value.release_error();
// Resulting value is in the accumulator.
Expand Down Expand Up @@ -751,9 +751,19 @@ void async_block_start(VM& vm, NonnullRefPtr<Statement const> const& async_body,
auto& running_context = vm.running_execution_context();

// 3. Set the code evaluation state of asyncContext such that when evaluation is resumed for that execution context the following steps will be performed:
auto execution_steps = NativeFunction::create(realm, "", [&async_body, &promise_capability, &async_context](auto& vm) -> ThrowCompletionOr<Value> {
auto execution_steps = NativeFunction::create(realm, "", [&realm, &async_body, &promise_capability, &async_context](auto& vm) -> ThrowCompletionOr<Value> {
// a. Let result be the result of evaluating asyncBody.
auto result = async_body->execute(vm.interpreter());
Completion result;
if (auto* bytecode_interpreter = vm.bytecode_interpreter_if_exists()) {
// FIXME: Cache this executable somewhere.
auto maybe_executable = Bytecode::compile(vm, async_body, FunctionKind::Async, "AsyncBlockStart"sv);
if (maybe_executable.is_error())
result = maybe_executable.release_error();
else
result = bytecode_interpreter->run_and_return_frame(realm, *maybe_executable.value(), nullptr).value;
} else {
result = async_body->execute(vm.interpreter());
}

// b. Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done.

Expand Down Expand Up @@ -817,7 +827,7 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
if (m_kind == FunctionKind::AsyncGenerator)
return vm.throw_completion<InternalError>(ErrorType::NotImplemented, "Async Generator function execution");

auto* bytecode_interpreter = Bytecode::Interpreter::current();
auto* bytecode_interpreter = vm.bytecode_interpreter_if_exists();

// The bytecode interpreter can execute generator functions while the AST interpreter cannot.
// This simply makes it create a new bytecode interpreter when one doesn't exist when executing a generator function.
Expand All @@ -826,32 +836,11 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
// However, this does cause an awkward situation with features not supported in bytecode, where features that work outside of generators with AST
// suddenly stop working inside of generators.
// This is a stop gap until bytecode mode becomes the default.
OwnPtr<Bytecode::Interpreter> temp_bc_interpreter;
if (m_kind == FunctionKind::Generator && !bytecode_interpreter) {
temp_bc_interpreter = make<Bytecode::Interpreter>(realm);
bytecode_interpreter = temp_bc_interpreter.ptr();
bytecode_interpreter = &vm.bytecode_interpreter();
}

if (bytecode_interpreter) {
auto compile = [&](auto& node, auto kind, auto name) -> ThrowCompletionOr<NonnullOwnPtr<Bytecode::Executable>> {
auto executable_result = Bytecode::Generator::generate(node, kind);
if (executable_result.is_error())
return vm.throw_completion<InternalError>(ErrorType::NotImplemented, TRY_OR_THROW_OOM(vm, executable_result.error().to_string()));

auto bytecode_executable = executable_result.release_value();
bytecode_executable->name = name;
auto& passes = Bytecode::Interpreter::optimization_pipeline();
passes.perform(*bytecode_executable);
if constexpr (JS_BYTECODE_DEBUG) {
dbgln("Optimisation passes took {}us", passes.elapsed());
dbgln("Compiled Bytecode::Block for function '{}':", m_name);
}
if (Bytecode::g_dump_bytecode)
bytecode_executable->dump();

return bytecode_executable;
};

// NOTE: There's a subtle ordering issue here:
// - We have to compile the default parameter values before instantiating the function.
// - We have to instantiate the function before compiling the function body.
Expand All @@ -864,18 +853,18 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
for (auto& parameter : m_formal_parameters) {
if (!parameter.default_value)
continue;
auto executable = TRY(compile(*parameter.default_value, FunctionKind::Normal, DeprecatedString::formatted("default parameter #{} for {}", default_parameter_index, m_name)));
auto executable = TRY(Bytecode::compile(vm, *parameter.default_value, FunctionKind::Normal, DeprecatedString::formatted("default parameter #{} for {}", default_parameter_index, m_name)));
m_default_parameter_bytecode_executables.append(move(executable));
}
}

TRY(function_declaration_instantiation(nullptr));

if (!m_bytecode_executable) {
m_bytecode_executable = TRY(compile(*m_ecmascript_code, m_kind, m_name));
m_bytecode_executable = TRY(Bytecode::compile(vm, *m_ecmascript_code, m_kind, m_name));
}

auto result_and_frame = bytecode_interpreter->run_and_return_frame(*m_bytecode_executable, nullptr);
auto result_and_frame = bytecode_interpreter->run_and_return_frame(realm, *m_bytecode_executable, nullptr);

VERIFY(result_and_frame.frame != nullptr);
if (result_and_frame.value.is_error())
Expand Down
17 changes: 3 additions & 14 deletions Userland/Libraries/LibJS/Runtime/GeneratorObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,18 +104,7 @@ ThrowCompletionOr<Value> GeneratorObject::execute(VM& vm, Completion const& comp
completion_object->define_direct_property(vm.names.type, Value(to_underlying(completion.type())), default_attributes);
completion_object->define_direct_property(vm.names.value, completion.value().value(), default_attributes);

auto* bytecode_interpreter = Bytecode::Interpreter::current();

// If we're coming from a context which has no bytecode interpreter, e.g. from AST mode calling Generate.prototype.next,
// we need to make one to be able to continue, as generators are only supported in bytecode mode.
// See also ECMAScriptFunctionObject::ordinary_call_evaluate_body where this is done as well.
OwnPtr<Bytecode::Interpreter> temp_bc_interpreter;
if (!bytecode_interpreter) {
temp_bc_interpreter = make<Bytecode::Interpreter>(realm);
bytecode_interpreter = temp_bc_interpreter.ptr();
}

VERIFY(bytecode_interpreter);
auto& bytecode_interpreter = vm.bytecode_interpreter();

auto const* next_block = generated_continuation(m_previous_value);

Expand All @@ -131,9 +120,9 @@ ThrowCompletionOr<Value> GeneratorObject::execute(VM& vm, Completion const& comp
if (frame)
frame->registers[0] = completion_object;
else
bytecode_interpreter->accumulator() = completion_object;
bytecode_interpreter.accumulator() = completion_object;

auto next_result = bytecode_interpreter->run_and_return_frame(*m_generating_function->bytecode_executable(), next_block, frame);
auto next_result = bytecode_interpreter.run_and_return_frame(realm, *m_generating_function->bytecode_executable(), next_block, frame);

vm.pop_execution_context();

Expand Down
2 changes: 2 additions & 0 deletions Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* SPDX-License-Identifier: BSD-2-Clause
*/

#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Lexer.h>
#include <LibJS/Parser.h>
Expand Down
Loading

0 comments on commit a4241fd

Please sign in to comment.