LibJS: Move bytecode interpreter state to VM

The bytecode interpreter only needed the running execution context,
but still threaded a separate Interpreter object through both the C++
and asm entry points. Move that state and the bytecode execution
helpers onto VM instead, and teach the asm generator and slow paths to
use VM directly.
This commit is contained in:
Andreas Kling 2026-04-13 11:54:04 +02:00 committed by Andreas Kling
parent ff5273084d
commit 2ca7dfa649
37 changed files with 1170 additions and 1266 deletions

View file

@ -197,11 +197,11 @@ fn generate_exit_point(out: &mut String, fmt: ObjectFormat) {
}
fn generate_entry_point(out: &mut String, program: &Program) {
// void asm_interpreter_entry(u8 const* bytecode, u32 entry_point, Value* values, Interpreter* interp)
// AAPCS64: x0=bytecode, w1=entry_point, x2=values, x3=interp
// void asm_interpreter_entry(u8 const* bytecode, u32 entry_point, Value* values, VM* vm)
// AAPCS64: x0=bytecode, w1=entry_point, x2=values, x3=vm
// Save callee-saved registers and link register.
// Pinned: x19(dispatch), x20(interp), x21(ip), x26(pb), x27(values), x28(exec_ctx)
// Pinned: x19(dispatch), x20(vm), x21(ip), x26(pb), x27(values), x28(exec_ctx)
// x21 = ip (instruction pointer = pb + pc), the primary dispatch register.
// x25 is only used when DSL code writes to pc directly (rare).
// x22 = INT32_TAG, x23 = BOOLEAN_TAG, x24 = NAN_BASE_TAG (pinned constants).
@ -231,12 +231,12 @@ fn generate_entry_point(out: &mut String, program: &Program) {
w!(out, " .cfi_offset d8, -16");
// Set up pinned registers
// x0=bytecode (pb), w1=entry_point (pc), x2=values, x3=interp
// x0=bytecode (pb), w1=entry_point (pc), x2=values, x3=vm
let interp_ctx = program
.constants
.get("INTERPRETER_RUNNING_EXECUTION_CONTEXT")
.get("VM_RUNNING_EXECUTION_CONTEXT")
.copied()
.expect("INTERPRETER_RUNNING_EXECUTION_CONTEXT constant required");
.expect("VM_RUNNING_EXECUTION_CONTEXT constant required");
let canon_nan = program
.constants
.get("CANON_NAN_BITS")
@ -244,8 +244,8 @@ fn generate_entry_point(out: &mut String, program: &Program) {
.expect("CANON_NAN_BITS constant required");
w!(out, " mov x26, x0 // pb = bytecode base");
w!(out, " mov x27, x2 // values = values array");
// Store Interpreter* in x20 (callee-saved) for C++ calls, pin exec_ctx in x28
w!(out, " mov x20, x3 // interp = Interpreter*");
// Store VM* in x20 (callee-saved) for C++ calls, pin exec_ctx in x28
w!(out, " mov x20, x3 // vm = VM*");
emit_ldr64(out, "x28", "x3", interp_ctx);
w!(out, " // x28 = exec_ctx");
emit_symbol_addr(out, "x19", "asm_dispatch_table", program.object_format);
@ -276,7 +276,7 @@ fn generate_entry_point(out: &mut String, program: &Program) {
fn generate_fallback_handler(out: &mut String, program: &Program, _pinned: &PinnedConstants) {
w!(out, ".p2align 4");
w!(out, "asm_handler_fallback:");
// Set up args: x0=interp (x20), w1=pc (ip - pb)
// Set up args: x0=vm (x20), w1=pc (ip - pb)
w!(out, " mov x0, x20");
w!(out, " sub w1, w21, w26");
w!(out, " bl CSYM(asm_fallback_handler)");
@ -294,13 +294,13 @@ fn generate_fallback_handler(out: &mut String, program: &Program, _pinned: &Pinn
}
/// Emit instructions to reload exec_ctx (x28), pb (x26), and values (x27)
/// from the Interpreter* in x20. Uses x9 as scratch.
/// from the VM* in x20. Uses x9 as scratch.
fn emit_state_reload(out: &mut String, program: &Program) {
let interp_ctx = program
.constants
.get("INTERPRETER_RUNNING_EXECUTION_CONTEXT")
.get("VM_RUNNING_EXECUTION_CONTEXT")
.copied()
.expect("INTERPRETER_RUNNING_EXECUTION_CONTEXT constant required");
.expect("VM_RUNNING_EXECUTION_CONTEXT constant required");
let exec_executable = program
.constants
.get("EXECUTION_CONTEXT_EXECUTABLE")
@ -921,13 +921,13 @@ fn emit_instruction(
}
}
// reload_exec_ctx: reload the pinned exec_ctx register from Interpreter* (x20)
// reload_exec_ctx: reload the pinned exec_ctx register from VM* (x20)
"reload_exec_ctx" => {
let interp_ctx = program
.constants
.get("INTERPRETER_RUNNING_EXECUTION_CONTEXT")
.get("VM_RUNNING_EXECUTION_CONTEXT")
.copied()
.expect("INTERPRETER_RUNNING_EXECUTION_CONTEXT constant required");
.expect("VM_RUNNING_EXECUTION_CONTEXT constant required");
emit_ldr64(out, "x28", "x20", interp_ctx);
}
@ -951,7 +951,7 @@ fn emit_instruction(
// call_slow_path: TERMINAL call to C++ slow path
"call_slow_path" => {
if let Some(Operand::Register(func_name)) = insn.operands.first() {
w!(out, " mov x0, x20"); // interp
w!(out, " mov x0, x20"); // vm
w!(out, " sub w1, w21, w26"); // pc = ip - pb
w!(out, " bl CSYM({func_name})");
w!(out, " tbnz x0, #63, .Lexit");
@ -973,7 +973,7 @@ fn emit_instruction(
}
}
// call_interp: NON-TERMINAL call with (Interpreter*, u32 pc)
// call_interp: NON-TERMINAL call with (VM*, u32 pc)
"call_interp" => {
if let Some(Operand::Register(func_name)) = insn.operands.first() {
w!(out, " mov x0, x20");

View file

@ -145,8 +145,8 @@ fn generate_exit_point(out: &mut String, fmt: ObjectFormat) {
}
fn generate_entry_point(out: &mut String, program: &Program) {
// void asm_interpreter_entry(u8 const* bytecode, u32 entry_point, Value* values, Interpreter* interp)
// System V AMD64: rdi=bytecode, esi=entry_point, rdx=values, rcx=interp
// void asm_interpreter_entry(u8 const* bytecode, u32 entry_point, Value* values, VM* vm)
// System V AMD64: rdi=bytecode, esi=entry_point, rdx=values, rcx=vm
// Save callee-saved registers
w!(out, " push rbp");
@ -168,19 +168,19 @@ fn generate_entry_point(out: &mut String, program: &Program) {
w!(out, " sub rsp, 8");
// Set up pinned registers
// rdi=bytecode (pb), esi=entry_point (pc), rdx=values, rcx=interp
// rdi=bytecode (pb), esi=entry_point (pc), rdx=values, rcx=vm
w!(out, " mov r14, rdi # pb = bytecode base");
w!(out, " mov r13d, esi # pc = entry_point");
w!(out, " mov r15, rdx # values = values array");
// Store Interpreter* on the stack for C++ calls, pin exec_ctx instead
// Store VM* on the stack for C++ calls, pin exec_ctx instead
let interp_ctx = program
.constants
.get("INTERPRETER_RUNNING_EXECUTION_CONTEXT")
.get("VM_RUNNING_EXECUTION_CONTEXT")
.copied()
.expect("INTERPRETER_RUNNING_EXECUTION_CONTEXT constant required");
.expect("VM_RUNNING_EXECUTION_CONTEXT constant required");
w!(
out,
" mov QWORD PTR [rbp - 48], rcx # save Interpreter*"
" mov QWORD PTR [rbp - 48], rcx # save VM*"
);
w!(
out,
@ -199,11 +199,11 @@ fn generate_entry_point(out: &mut String, program: &Program) {
fn generate_fallback_handler(out: &mut String, program: &Program) {
// The fallback handler calls into C++ for any unhandled instruction.
// extern "C" i64 asm_fallback_handler(Interpreter* interp, u32 pc);
// extern "C" i64 asm_fallback_handler(VM* vm, u32 pc);
// Returns >= 0: new pc to dispatch to. Returns < 0: exit.
w!(out, ".p2align 4");
w!(out, "asm_handler_fallback:");
// Set up args: rdi=interp (from stack), esi=pc (r13d)
// Set up args: rdi=vm (from stack), esi=pc (r13d)
w!(out, " mov rdi, QWORD PTR [rbp - 48]");
w!(out, " mov esi, r13d");
w!(out, " call CSYM(asm_fallback_handler)");
@ -223,13 +223,13 @@ fn generate_fallback_handler(out: &mut String, program: &Program) {
}
/// Emit instructions to reload exec_ctx (rbx), pb (r14) and values (r15)
/// from the Interpreter* saved on the stack. Uses rcx as scratch.
/// from the VM* saved on the stack. Uses rcx as scratch.
fn emit_state_reload(out: &mut String, program: &Program) {
let interp_ctx = program
.constants
.get("INTERPRETER_RUNNING_EXECUTION_CONTEXT")
.get("VM_RUNNING_EXECUTION_CONTEXT")
.copied()
.expect("INTERPRETER_RUNNING_EXECUTION_CONTEXT constant required");
.expect("VM_RUNNING_EXECUTION_CONTEXT constant required");
let exec_executable = program
.constants
.get("EXECUTION_CONTEXT_EXECUTABLE")
@ -401,13 +401,13 @@ fn emit_instruction(out: &mut String, insn: &AsmInstruction, handler: &Handler,
}
}
// reload_exec_ctx: reload the pinned exec_ctx register from Interpreter*
// reload_exec_ctx: reload the pinned exec_ctx register from VM*
"reload_exec_ctx" => {
let interp_ctx = program
.constants
.get("INTERPRETER_RUNNING_EXECUTION_CONTEXT")
.get("VM_RUNNING_EXECUTION_CONTEXT")
.copied()
.expect("INTERPRETER_RUNNING_EXECUTION_CONTEXT constant required");
.expect("VM_RUNNING_EXECUTION_CONTEXT constant required");
w!(out, " mov rcx, QWORD PTR [rbp - 48]");
w!(out, " mov rbx, QWORD PTR [rcx + {interp_ctx}]");
}
@ -429,14 +429,14 @@ fn emit_instruction(out: &mut String, insn: &AsmInstruction, handler: &Handler,
}
// call_slow_path pseudo-instruction
// Calls a slow path C function: i64 func(Interpreter*, u32 pc)
// Calls a slow path C function: i64 func(VM*, u32 pc)
// Returns >= 0: new pc to dispatch to. Returns < 0: exit.
// This is TERMINAL: it dispatches after return, control doesn't come back.
// After the call, pb and values are reloaded from the running execution
// context, since exception handling may have unwound inline frames.
"call_slow_path" => {
if let Some(Operand::Register(func_name)) = insn.operands.first() {
// Set up args: rdi=interp (from stack), esi=pc
// Set up args: rdi=vm (from stack), esi=pc
w!(out, " mov rdi, QWORD PTR [rbp - 48]");
w!(out, " mov esi, r13d");
w!(out, " call CSYM({func_name})");
@ -465,7 +465,7 @@ fn emit_instruction(out: &mut String, insn: &AsmInstruction, handler: &Handler,
}
// call_interp pseudo-instruction
// Calls a C++ helper: i64 func(Interpreter*, u32 pc)
// Calls a C++ helper: i64 func(VM*, u32 pc)
// Same args as call_slow_path, but NON-TERMINAL: result in rax (t0),
// handler continues after.
"call_interp" => {

View file

@ -19,7 +19,7 @@
//! 3. Opcodes without an assembly handler are caught by a **fallback handler**
//! that calls into C++ (`asm_fallback_handler`).
//!
//! The entry point is `asm_interpreter_entry(bytecode, entry_point, values, interp)`.
//! The entry point is `asm_interpreter_entry(bytecode, entry_point, values, vm)`.
//! It saves callee-saved registers, sets up pinned registers, and dispatches.
//!
//! ## Pinned registers
@ -61,7 +61,7 @@
//!
//! ### C++ interop
//!
//! - `call_slow_path func` -- **TERMINAL.** Calls `i64 func(Interpreter*, u32 pc)`.
//! - `call_slow_path func` -- **TERMINAL.** Calls `i64 func(VM*, u32 pc)`.
//! If return >= 0, reloads pinned state (exec_ctx, pb, values -- they may
//! have changed due to exception unwinding), sets pc to the return value,
//! and dispatches. If return < 0, exits. Control does NOT return to the
@ -69,9 +69,9 @@
//! - `call_helper func` -- **Non-terminal.** Calls `u64 func(u64 value)`.
//! Passes `t1` as the argument. Result lands in `t0`. The handler continues
//! after the call. Does NOT reload pinned state.
//! - `call_interp func` -- **Non-terminal.** Calls `i64 func(Interpreter*, u32 pc)`.
//! - `call_interp func` -- **Non-terminal.** Calls `i64 func(VM*, u32 pc)`.
//! Result lands in `t0`. The handler continues. Does NOT reload pinned state.
//! - `reload_exec_ctx` -- Reload the exec_ctx register from the Interpreter*.
//! - `reload_exec_ctx` -- Reload the exec_ctx register from the VM*.
//! Used after non-terminal calls that may modify the running execution context.
//!
//! ### Bytecode operand access

File diff suppressed because it is too large Load diff

View file

@ -8,14 +8,16 @@
#include <AK/Types.h>
namespace JS::Bytecode {
namespace JS {
class Interpreter;
class VM;
namespace Bytecode {
class AsmInterpreter {
public:
static void run(Interpreter&, size_t entry_point);
static void run(VM&, size_t entry_point);
static bool is_available();
};
}
}

View file

@ -12,7 +12,6 @@
#include <AK/Utf16StringData.h>
#include <LibJS/Bytecode/Builtins.h>
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Bytecode/PropertyNameIterator.h>
#include <LibJS/Bytecode/PutKind.h>
#include <LibJS/Runtime/ArrayBuffer.h>
@ -25,6 +24,7 @@
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/Shape.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibJS/Runtime/VM.h>
#define EMIT_OFFSET(name, type, member) \
outln("const " #name " = {}", offsetof(type, member))
@ -139,9 +139,9 @@ int main()
EMIT_OFFSET(REALM_GLOBAL_OBJECT, Realm, m_global_object);
EMIT_OFFSET(REALM_GLOBAL_DECLARATIVE_ENVIRONMENT, Realm, m_global_declarative_environment);
// Interpreter layout
outln("\n# Interpreter layout");
EMIT_OFFSET(INTERPRETER_RUNNING_EXECUTION_CONTEXT, Interpreter, m_running_execution_context);
// VM layout
outln("\n# VM layout");
EMIT_OFFSET(VM_RUNNING_EXECUTION_CONTEXT, VM, m_running_execution_context);
// IndexedStorageKind enum values
outln("\n# IndexedStorageKind enum values");

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Export.h>
namespace JS::Bytecode {
JS_API extern bool g_dump_bytecode;
}

File diff suppressed because it is too large Load diff

View file

@ -1,119 +0,0 @@
/*
* Copyright (c) 2021, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Bytecode/BuiltinAbstractOperationsEnabled.h>
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Bytecode/Label.h>
#include <LibJS/Bytecode/Register.h>
#include <LibJS/Export.h>
#include <LibJS/Forward.h>
#include <LibJS/Heap/Cell.h>
#include <LibJS/Runtime/FunctionKind.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/Value.h>
namespace JS::Bytecode {
class InstructionStreamIterator;
class JS_API Interpreter {
public:
Interpreter();
~Interpreter();
[[nodiscard]] Realm& realm() { return *m_running_execution_context->realm; }
[[nodiscard]] Object& global_object() { return realm().global_object(); }
[[nodiscard]] DeclarativeEnvironment& global_declarative_environment();
static VM& vm() { return VM::the(); }
ThrowCompletionOr<Value> run(Script&, GC::Ptr<Environment> lexical_environment_override = nullptr);
ThrowCompletionOr<Value> run(SourceTextModule&);
ThrowCompletionOr<Value> run_executable(ExecutionContext&, Executable&, u32 entry_point = 0);
ThrowCompletionOr<Value> run_executable(ExecutionContext& context, Executable& executable, u32 entry_point, Value initial_accumulator_value)
{
context.registers_and_constants_and_locals_and_arguments_span()[0] = initial_accumulator_value;
return run_executable(context, executable, entry_point);
}
ALWAYS_INLINE Value& accumulator() { return reg(Register::accumulator()); }
Value& reg(Register const& r)
{
return m_running_execution_context->registers_and_constants_and_locals_and_arguments()[r.index()];
}
Value reg(Register const& r) const
{
return m_running_execution_context->registers_and_constants_and_locals_and_arguments()[r.index()];
}
ALWAYS_INLINE Value get(Operand op) const
{
return m_running_execution_context->registers_and_constants_and_locals_and_arguments()[op.raw()];
}
ALWAYS_INLINE void set(Operand op, Value value)
{
m_running_execution_context->registers_and_constants_and_locals_and_arguments_span().data()[op.raw()] = value;
}
Value do_yield(Value value, Optional<Label> continuation);
void do_return(Value value)
{
if (value.is_special_empty_value())
value = js_undefined();
reg(Register::return_value()) = value;
reg(Register::exception()) = js_special_empty_value();
}
void catch_exception(Operand dst);
Executable& current_executable() { return *m_running_execution_context->executable; }
Executable const& current_executable() const { return *m_running_execution_context->executable; }
ExecutionContext& running_execution_context() { return *m_running_execution_context; }
void set_running_execution_context(ExecutionContext* ctx) { m_running_execution_context = ctx; }
[[nodiscard]] Utf16FlyString const& get_identifier(IdentifierTableIndex) const;
[[nodiscard]] Optional<Utf16FlyString const&> get_identifier(Optional<IdentifierTableIndex> index) const
{
if (!index.has_value())
return {};
return get_identifier(*index);
}
[[nodiscard]] PropertyKey const& get_property_key(PropertyKeyTableIndex) const;
enum class HandleExceptionResponse {
ExitFromExecutable,
ContinueInThisExecutable,
};
[[nodiscard]] COLD HandleExceptionResponse handle_exception(u32 program_counter, Value exception);
[[nodiscard]] NEVER_INLINE bool try_inline_call(Instruction const&, u32 current_pc);
[[nodiscard]] NEVER_INLINE bool try_inline_call_construct(Instruction const&, u32 current_pc);
NEVER_INLINE void pop_inline_frame(Value return_value);
ExecutionContext* push_inline_frame(
ECMAScriptFunctionObject& callee_function,
Executable& callee_executable,
ReadonlySpan<Operand> arguments,
u32 return_pc,
u32 dst_raw,
Value this_value,
Object* new_target,
bool is_construct);
private:
void run_bytecode(size_t entry_point);
ExecutionContext* m_running_execution_context { nullptr };
};
JS_API extern bool g_dump_bytecode;
}

View file

@ -8,7 +8,6 @@
// NOTE: This file is not named $262Object.cpp because dollar signs in file names cause issues with some build tools.
#include <AK/TypeCasts.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Contrib/Test262/262Object.h>
#include <LibJS/Contrib/Test262/AgentObject.h>
#include <LibJS/Contrib/Test262/GlobalObject.h>
@ -17,6 +16,7 @@
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Script.h>
namespace JS::Test262 {
@ -106,7 +106,7 @@ JS_DEFINE_NATIVE_FUNCTION($262Object::eval_script)
}
// 5. Let status be ScriptEvaluation(s).
auto status = vm.bytecode_interpreter().run(script_or_error.value());
auto status = vm.run(script_or_error.value());
// 6. Return Completion(status).
return status;

View file

@ -319,7 +319,6 @@ enum class Builtin : u8;
class Executable;
class Generator;
class Instruction;
class Interpreter;
class Operand;
struct PropertyLookupCache;
class RegexTable;

View file

@ -9,7 +9,7 @@
#include <AK/Function.h>
#include <AK/Optional.h>
#include <AK/Utf16View.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Bytecode/Debug.h>
#include <LibJS/ModuleLoading.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Accessor.h>
@ -37,6 +37,7 @@
#include <LibJS/Runtime/StringPrototype.h>
#include <LibJS/Runtime/SuppressedError.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibJS/RustIntegration.h>
#include <LibJS/SourceCode.h>
@ -773,7 +774,7 @@ ThrowCompletionOr<Value> perform_eval(VM& vm, Value x, CallerMode strict_caller,
Optional<Value> result;
result = TRY(vm.bytecode_interpreter().run_executable(*eval_context, *executable, {}));
result = TRY(vm.run_executable(*eval_context, *executable, {}));
// 32. If result.[[Type]] is normal and result.[[Value]] is empty, then
// a. Set result to NormalCompletion(undefined).

View file

@ -6,11 +6,11 @@
#pragma once
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/GeneratorObject.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/Promise.h>
#include <LibJS/Runtime/VM.h>
namespace JS {

View file

@ -167,15 +167,13 @@ void AsyncGenerator::execute(VM& vm, Completion completion)
while (true) {
auto completion_cell = heap().allocate<CompletionCell>(completion);
auto& bytecode_interpreter = vm.bytecode_interpreter();
// We should never enter `execute` again after the generator is complete.
VERIFY(m_yield_continuation != ExecutionContext::no_yield_continuation);
// Clear yield state so that a normal return (no yield) is detected as done.
m_async_generator_context->yield_continuation = ExecutionContext::no_yield_continuation;
auto result_value = bytecode_interpreter.run_executable(vm.running_execution_context(), m_generating_executable, m_yield_continuation, completion_cell);
auto result_value = vm.run_executable(vm.running_execution_context(), m_generating_executable, m_yield_continuation, completion_cell);
if (result_value.is_throw_completion()) {
m_yield_continuation = ExecutionContext::no_yield_continuation;

View file

@ -8,9 +8,9 @@
#pragma once
#include <AK/Variant.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Runtime/ExecutionContext.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/VM.h>
namespace JS {

View file

@ -10,7 +10,7 @@
#include <AK/Debug.h>
#include <AK/Function.h>
#include <LibGC/DeferGC.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Bytecode/Debug.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/AsyncFunctionDriverWrapper.h>
@ -25,6 +25,7 @@
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/PromiseCapability.h>
#include <LibJS/Runtime/PromiseConstructor.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibJS/RustIntegration.h>
@ -516,7 +517,7 @@ template void async_function_start(VM&, PromiseCapability const&, GC::Function<C
// 15.8.4 Runtime Semantics: EvaluateAsyncFunctionBody, https://tc39.es/ecma262/#sec-runtime-semantics-evaluatefunctionbody
ThrowCompletionOr<Value> ECMAScriptFunctionObject::ordinary_call_evaluate_body(VM& vm, ExecutionContext& context)
{
auto result = TRY(vm.bytecode_interpreter().run_executable(context, *bytecode_executable(), {}));
auto result = TRY(vm.run_executable(context, *bytecode_executable(), {}));
// NOTE: Running the bytecode should eventually return a completion.
// Until it does, we assume "return" and include the undefined fallback from the call site.

View file

@ -8,12 +8,12 @@
#pragma once
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Export.h>
#include <LibJS/Runtime/ClassFieldDefinition.h>
#include <LibJS/Runtime/ExecutionContext.h>
#include <LibJS/Runtime/FunctionObject.h>
#include <LibJS/Runtime/SharedFunctionInstanceData.h>
#include <LibJS/Runtime/VM.h>
namespace JS {
@ -111,7 +111,7 @@ public:
bool allocates_function_environment() const { return shared_data().m_function_environment_needed; }
friend class Bytecode::Generator;
friend class Bytecode::Interpreter;
friend class VM;
private:
ECMAScriptFunctionObject(

View file

@ -123,7 +123,7 @@ public:
u32 caller_dst_raw { 0 };
private:
friend class Bytecode::Interpreter;
friend class VM;
Value* registers_and_constants_and_locals_and_arguments()
{

View file

@ -5,13 +5,13 @@
*/
#include <AK/TemporaryChange.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Runtime/CompletionCell.h>
#include <LibJS/Runtime/GeneratorObject.h>
#include <LibJS/Runtime/GeneratorPrototype.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Iterator.h>
#include <LibJS/Runtime/NativeJavaScriptBackedFunction.h>
#include <LibJS/Runtime/VM.h>
namespace JS {
@ -104,15 +104,13 @@ ThrowCompletionOr<GeneratorObject::IterationResult> GeneratorObject::execute(VM&
auto completion_cell = heap().allocate<CompletionCell>(completion);
auto& bytecode_interpreter = vm.bytecode_interpreter();
// We should never enter `execute` again after the generator is complete.
VERIFY(m_yield_continuation != ExecutionContext::no_yield_continuation);
// Clear yield state so that a normal return (no yield) is detected as done.
m_execution_context->yield_continuation = ExecutionContext::no_yield_continuation;
auto result_value = bytecode_interpreter.run_executable(vm.running_execution_context(), *m_generating_executable, m_yield_continuation, completion_cell);
auto result_value = vm.run_executable(vm.running_execution_context(), *m_generating_executable, m_yield_continuation, completion_cell);
vm.pop_execution_context();

View file

@ -6,9 +6,9 @@
#pragma once
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/VM.h>
namespace JS {

View file

@ -5,11 +5,11 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Runtime/FunctionEnvironment.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/NativeJavaScriptBackedFunction.h>
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/Value.h>
namespace JS {

View file

@ -5,11 +5,12 @@
*/
#include <AK/TypeCasts.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Bytecode/Debug.h>
#include <LibJS/Runtime/AsyncFunctionDriverWrapper.h>
#include <LibJS/Runtime/AsyncGenerator.h>
#include <LibJS/Runtime/GeneratorObject.h>
#include <LibJS/Runtime/NativeJavaScriptBackedFunction.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/RustIntegration.h>
namespace JS {
@ -72,7 +73,7 @@ ThrowCompletionOr<Value> NativeJavaScriptBackedFunction::call()
{
auto& vm = this->vm();
auto result = TRY(vm.bytecode_interpreter().run_executable(vm.running_execution_context(), bytecode_executable(), {}));
auto result = TRY(vm.run_executable(vm.running_execution_context(), bytecode_executable(), {}));
auto kind = this->kind();
if (kind == FunctionKind::Normal)

View file

@ -15,7 +15,6 @@
#include <AK/Time.h>
#include <LibFileSystem/FileSystem.h>
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/ArrayBuffer.h>
@ -78,7 +77,6 @@ VM::VM(ErrorMessages error_messages)
, m_error_messages(move(error_messages))
{
s_the = this;
m_bytecode_interpreter = make<Bytecode::Interpreter>();
m_heap.register_sweep_callback([] {
Bytecode::StaticPropertyLookupCache::sweep_all();
@ -532,16 +530,19 @@ void VM::dump_backtrace() const
void VM::save_execution_context_stack()
{
m_saved_execution_context_stacks.append(move(m_execution_context_stack));
m_running_execution_context = nullptr;
}
void VM::clear_execution_context_stack()
{
m_execution_context_stack.clear_with_capacity();
m_running_execution_context = nullptr;
}
void VM::restore_execution_context_stack()
{
m_execution_context_stack = m_saved_execution_context_stacks.take_last();
m_running_execution_context = m_execution_context_stack.is_empty() ? nullptr : m_execution_context_stack.last();
}
// 9.4.1 GetActiveScriptOrModule ( ), https://tc39.es/ecma262/#sec-getactivescriptormodule
@ -583,9 +584,9 @@ VM::StoredModule* VM::get_stored_module(ImportedModuleReferrer const&, ByteStrin
return &(*end_or_module);
}
ThrowCompletionOr<void> VM::link_and_eval_module(Badge<Bytecode::Interpreter>, SourceTextModule& module)
ThrowCompletionOr<void> VM::link_and_eval_module(SourceTextModule& module)
{
return link_and_eval_module(module);
return link_and_eval_module(static_cast<CyclicModule&>(module));
}
ThrowCompletionOr<void> VM::link_and_eval_module(CyclicModule& module)

View file

@ -19,6 +19,10 @@
#include <LibGC/Function.h>
#include <LibGC/Heap.h>
#include <LibGC/RootVector.h>
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Bytecode/Label.h>
#include <LibJS/Bytecode/Operand.h>
#include <LibJS/Bytecode/Register.h>
#include <LibJS/CyclicModule.h>
#include <LibJS/Export.h>
#include <LibJS/ModuleLoading.h>
@ -63,7 +67,83 @@ public:
GC::Heap& heap() const { return const_cast<GC::Heap&>(m_heap); }
Bytecode::Interpreter& bytecode_interpreter() { return *m_bytecode_interpreter; }
VM& vm() { return *this; }
VM const& vm() const { return *this; }
[[nodiscard]] Realm& realm() { return *m_running_execution_context->realm; }
[[nodiscard]] Object& global_object() { return realm().global_object(); }
[[nodiscard]] DeclarativeEnvironment& global_declarative_environment();
ThrowCompletionOr<Value> run(Script&, GC::Ptr<Environment> lexical_environment_override = nullptr);
ThrowCompletionOr<Value> run(SourceTextModule&);
ThrowCompletionOr<Value> run_executable(ExecutionContext&, Bytecode::Executable&, u32 entry_point = 0);
ThrowCompletionOr<Value> run_executable(ExecutionContext& context, Bytecode::Executable& executable, u32 entry_point, Value initial_accumulator_value)
{
context.registers_and_constants_and_locals_and_arguments_span()[0] = initial_accumulator_value;
return run_executable(context, executable, entry_point);
}
ALWAYS_INLINE Value& accumulator() { return reg(Bytecode::Register::accumulator()); }
Value& reg(Bytecode::Register const& r)
{
return m_running_execution_context->registers_and_constants_and_locals_and_arguments()[r.index()];
}
Value reg(Bytecode::Register const& r) const
{
return m_running_execution_context->registers_and_constants_and_locals_and_arguments()[r.index()];
}
ALWAYS_INLINE Value get(Bytecode::Operand op) const
{
return m_running_execution_context->registers_and_constants_and_locals_and_arguments()[op.raw()];
}
ALWAYS_INLINE void set(Bytecode::Operand op, Value value)
{
m_running_execution_context->registers_and_constants_and_locals_and_arguments_span().data()[op.raw()] = value;
}
Value do_yield(Value value, Optional<Bytecode::Label> continuation);
void do_return(Value value)
{
if (value.is_special_empty_value())
value = js_undefined();
reg(Bytecode::Register::return_value()) = value;
reg(Bytecode::Register::exception()) = js_special_empty_value();
}
void catch_exception(Bytecode::Operand dst);
Bytecode::Executable& current_executable() { return *m_running_execution_context->executable; }
Bytecode::Executable const& current_executable() const { return *m_running_execution_context->executable; }
[[nodiscard]] Utf16FlyString const& get_identifier(Bytecode::IdentifierTableIndex) const;
[[nodiscard]] Optional<Utf16FlyString const&> get_identifier(Optional<Bytecode::IdentifierTableIndex> index) const
{
if (!index.has_value())
return {};
return get_identifier(*index);
}
[[nodiscard]] PropertyKey const& get_property_key(Bytecode::PropertyKeyTableIndex) const;
enum class HandleExceptionResponse {
ExitFromExecutable,
ContinueInThisExecutable,
};
[[nodiscard]] COLD HandleExceptionResponse handle_exception(u32 program_counter, Value exception);
NEVER_INLINE void pop_inline_frame(Value return_value);
ExecutionContext* push_inline_frame(
ECMAScriptFunctionObject& callee_function,
Bytecode::Executable& callee_executable,
ReadonlySpan<Bytecode::Operand> arguments,
u32 return_pc,
u32 dst_raw,
Value this_value,
Object* new_target,
bool is_construct);
void dump_backtrace() const;
@ -128,17 +208,20 @@ public:
return throw_completion<InternalError>(ErrorType::CallStackSizeExceeded);
}
m_execution_context_stack.append(&context);
m_running_execution_context = &context;
return {};
}
void push_execution_context(ExecutionContext& context)
{
m_execution_context_stack.append(&context);
m_running_execution_context = &context;
}
void pop_execution_context()
{
m_execution_context_stack.take_last();
m_running_execution_context = m_execution_context_stack.is_empty() ? nullptr : m_execution_context_stack.last();
}
// https://tc39.es/ecma262/#running-execution-context
@ -146,11 +229,13 @@ public:
// This is known as the agent's running execution context.
ExecutionContext& running_execution_context()
{
return *m_execution_context_stack.last();
VERIFY(m_running_execution_context);
return *m_running_execution_context;
}
ExecutionContext const& running_execution_context() const
{
return *m_execution_context_stack.last();
VERIFY(m_running_execution_context);
return *m_running_execution_context;
}
// https://tc39.es/ecma262/#execution-context-stack
@ -271,9 +356,6 @@ public:
void clear_execution_context_stack();
void restore_execution_context_stack();
// Do not call this method unless you are sure this is the only and first module to be loaded in this vm.
ThrowCompletionOr<void> link_and_eval_module(Badge<Bytecode::Interpreter>, SourceTextModule& module);
ScriptOrModule get_active_script_or_module() const;
// 16.2.1.10 HostLoadImportedModule ( referrer, moduleRequest, hostDefined, payload ), https://tc39.es/ecma262/#sec-HostLoadImportedModule
@ -316,10 +398,15 @@ private:
void load_imported_module(ImportedModuleReferrer, ModuleRequest const&, GC::Ptr<GraphLoadingState::HostDefined>, ImportedModulePayload);
ThrowCompletionOr<void> link_and_eval_module(CyclicModule&);
ThrowCompletionOr<void> link_and_eval_module(SourceTextModule&);
void set_well_known_symbols(WellKnownSymbols well_known_symbols) { m_well_known_symbols = move(well_known_symbols); }
void run_queued_promise_jobs_impl();
void run_bytecode(size_t entry_point);
[[nodiscard]] NEVER_INLINE bool try_inline_call(Bytecode::Instruction const&, u32 current_pc);
[[nodiscard]] NEVER_INLINE bool try_inline_call_construct(Bytecode::Instruction const&, u32 current_pc);
static VM* s_the;
@ -332,6 +419,7 @@ private:
GC::Heap m_heap;
Vector<ExecutionContext*> m_execution_context_stack;
ExecutionContext* m_running_execution_context { nullptr };
Vector<Vector<ExecutionContext*>> m_saved_execution_context_stacks;
@ -368,8 +456,6 @@ private:
OwnPtr<Agent> m_agent;
OwnPtr<Bytecode::Interpreter> m_bytecode_interpreter;
bool m_dynamic_imports_allowed { false };
};

View file

@ -8,13 +8,13 @@
#include <AK/Debug.h>
#include <AK/QuickSort.h>
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Runtime/AsyncFunctionDriverWrapper.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/GlobalEnvironment.h>
#include <LibJS/Runtime/ModuleEnvironment.h>
#include <LibJS/Runtime/PromiseCapability.h>
#include <LibJS/Runtime/SharedFunctionInstanceData.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/RustIntegration.h>
#include <LibJS/Script.h>
#include <LibJS/SourceCode.h>
@ -551,7 +551,7 @@ ThrowCompletionOr<void> SourceTextModule::execute_module(VM& vm, GC::Ptr<Promise
// c. Let result be the result of evaluating module.[[ECMAScriptCode]].
Completion result;
auto result_or_error = vm.bytecode_interpreter().run_executable(*module_context, *m_executable, {});
auto result_or_error = vm.run_executable(*module_context, *m_executable, {});
if (result_or_error.is_error()) {
result = result_or_error.release_error();
} else {

View file

@ -19,13 +19,13 @@
#include <AK/Tuple.h>
#include <LibCore/DirIterator.h>
#include <LibCore/File.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/ParserError.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/JSONObject.h>
#include <LibJS/Runtime/Reference.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibJS/Runtime/WeakMap.h>
#include <LibJS/Runtime/WeakSet.h>
@ -389,7 +389,7 @@ inline JSFileResult TestRunner::run_file_test(ByteString const& test_path)
auto test_script = result.release_value();
g_vm->push_execution_context(global_execution_context);
MUST(g_vm->bytecode_interpreter().run(*test_script));
MUST(g_vm->run(*test_script));
g_vm->pop_execution_context();
auto file_script = parse_script(test_path, *realm);
@ -400,7 +400,7 @@ inline JSFileResult TestRunner::run_file_test(ByteString const& test_path)
return { test_path, file_script.error() };
}
g_vm->push_execution_context(global_execution_context);
top_level_result = g_vm->bytecode_interpreter().run(file_script.value());
top_level_result = g_vm->run(file_script.value());
g_vm->pop_execution_context();
g_vm->push_execution_context(global_execution_context);

View file

@ -11,6 +11,7 @@
#include <LibCore/Environment.h>
#include <LibCore/System.h>
#include <LibFileSystem/FileSystem.h>
#include <LibJS/Bytecode/Debug.h>
#include <LibTest/JavaScriptTestRunner.h>
#include <signal.h>
#include <stdio.h>

View file

@ -6,7 +6,7 @@
#include <AK/Debug.h>
#include <LibCore/ElapsedTimer.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Runtime/VM.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/HTML/Scripting/ClassicScript.h>
#include <LibWeb/HTML/Scripting/Environments.h>
@ -132,7 +132,7 @@ JS::Completion ClassicScript::run(RethrowErrors rethrow_errors, GC::Ptr<JS::Envi
else {
auto timer = Core::ElapsedTimer::start_new();
evaluation_status = vm().bytecode_interpreter().run(*m_script_record, lexical_environment_override);
evaluation_status = vm().run(*m_script_record, lexical_environment_override);
// FIXME: If ScriptEvaluation does not complete because the user agent has aborted the running script, leave evaluationStatus as null.

View file

@ -6,8 +6,8 @@
*/
#include <AK/StringView.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Script.h>
#include <stddef.h>
#include <stdint.h>
@ -24,7 +24,7 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
auto& realm = *root_execution_context->realm;
auto parse_result = JS::Script::parse(js, realm);
if (!parse_result.is_error())
(void)vm->bytecode_interpreter().run(parse_result.value());
(void)vm->run(parse_result.value());
return 0;
}

View file

@ -7,9 +7,9 @@
#include <AK/Format.h>
#include <AK/Function.h>
#include <AK/StringView.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Forward.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/VM.h>
#include <errno.h>
#include <stddef.h>
@ -224,7 +224,7 @@ int main(int, char**)
if (parse_result.is_error()) {
result = 1;
} else {
auto completion = vm->bytecode_interpreter().run(parse_result.value());
auto completion = vm->run(parse_result.value());
if (completion.is_error()) {
result = 1;
}

View file

@ -288,7 +288,7 @@ def generate_class(op: OpDef) -> str:
lines.append("")
ret_type = execute_return_type(op)
lines.append(f" {ret_type} execute_impl(Bytecode::Interpreter&) const;")
lines.append(f" {ret_type} execute_impl(VM&) const;")
lines.append(" ByteString to_byte_string_impl(Bytecode::Executable const&) const;")
visit_operands = generate_visit_operands(op)

View file

@ -17,7 +17,6 @@
#include <LibGfx/SkiaBackendContext.h>
#include <LibIPC/ConnectionFromClient.h>
#include <LibIPC/TransportHandle.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibMain/Main.h>
#include <LibRequests/RequestClient.h>
#include <LibUnicode/TimeZone.h>

View file

@ -37,7 +37,7 @@ TESTJS_GLOBAL_FUNCTION(evaluate_source, evaluateSource)
if (script.is_error())
return vm.throw_completion<JS::SyntaxError>(script.error().first().to_string());
return vm.bytecode_interpreter().run(script.value());
return vm.run(script.value());
}
TESTJS_GLOBAL_FUNCTION(run_queued_promise_jobs, runQueuedPromiseJobs)

View file

@ -13,7 +13,6 @@
#include <LibCore/System.h>
#include <LibIPC/ConnectionFromClient.h>
#include <LibImageDecoderClient/Client.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibMedia/Audio/Loader.h>
#include <LibRequests/RequestClient.h>
#include <LibWeb/Bindings/MainThreadVM.h>

View file

@ -13,7 +13,7 @@
#include <LibCore/ArgsParser.h>
#include <LibCore/ConfigFile.h>
#include <LibCore/StandardPaths.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Bytecode/Debug.h>
#include <LibJS/Console.h>
#include <LibJS/Contrib/Test262/GlobalObject.h>
#include <LibJS/Print.h>
@ -23,6 +23,7 @@
#include <LibJS/Runtime/JSONObject.h>
#include <LibJS/Runtime/Reference.h>
#include <LibJS/Runtime/StringPrototype.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibJS/RustFFI.h>
#include <LibJS/Script.h>
@ -213,7 +214,7 @@ static ErrorOr<bool> parse_and_run(JS::Realm& realm, StringView source, StringVi
auto script = script_or_error.release_value();
if (!parse_only)
result = vm.bytecode_interpreter().run(*script);
result = vm.run(*script);
}
} else {
auto module_or_error = JS::SourceTextModule::parse(source, realm, source_name);
@ -231,7 +232,7 @@ static ErrorOr<bool> parse_and_run(JS::Realm& realm, StringView source, StringVi
} else {
auto module = module_or_error.release_value();
if (!parse_only)
result = vm.bytecode_interpreter().run(*module);
result = vm.run(*module);
}
}

View file

@ -15,7 +15,6 @@
#include <LibCore/ElapsedTimer.h>
#include <LibCore/File.h>
#include <LibCore/System.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Contrib/Test262/GlobalObject.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/ValueInlines.h>
@ -235,7 +234,7 @@ static ErrorOr<void, TestError> run_test(StringView source, StringView filepath,
if (!harness_builder.is_empty()) {
ScriptOrModuleProgram harness_program { TRY(parse_harness_contents(*realm, harness_builder.string_view())) };
if (auto result = run_program(vm->bytecode_interpreter(), harness_program); result.is_error()) {
if (auto result = run_program(*vm, harness_program); result.is_error()) {
return TestError {
NegativePhase::Harness,
result.error().type,
@ -245,7 +244,7 @@ static ErrorOr<void, TestError> run_test(StringView source, StringView filepath,
}
}
return run_program(vm->bytecode_interpreter(), program);
return run_program(*vm, program);
}
static ErrorOr<TestMetadata, String> extract_metadata(StringView source)

View file

@ -16,7 +16,6 @@
#include <LibCore/MappedFile.h>
#include <LibCrypto/BigInt/SignedBigInteger.h>
#include <LibFileSystem/FileSystem.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/BigInt.h>
#include <LibJS/Runtime/VM.h>
@ -419,8 +418,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
}
auto js_script = script.release_value();
JS::Bytecode::Interpreter interp;
auto maybe_function = interp.run(*js_script);
auto maybe_function = vm->run(*js_script);
if (maybe_function.is_error()) {
warnln("Failed to run JS export source '{}'", js_function);
return false;