mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-23 08:00:20 +00:00
LibJS: Put Bytecode::CallFrame + register slots in a single allocation
The number of registers in a call frame never changes, so we can allocate it at the end of the CallFrame object and save ourselves the cost of allocating separate Vector storage for every call frame.
This commit is contained in:
parent
3dc5f467a8
commit
3fc0333ee6
Notes:
sideshowbarker
2024-07-17 06:00:02 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/3fc0333ee6 Pull-request: https://github.com/SerenityOS/serenity/pull/22071
11 changed files with 57 additions and 36 deletions
|
@ -40,6 +40,18 @@ namespace JS::Bytecode {
|
|||
|
||||
bool g_dump_bytecode = false;
|
||||
|
||||
NonnullOwnPtr<CallFrame> CallFrame::create(size_t register_count)
|
||||
{
|
||||
size_t allocation_size = sizeof(CallFrame) + sizeof(Value) * register_count;
|
||||
auto* memory = malloc(allocation_size);
|
||||
VERIFY(memory);
|
||||
auto call_frame = adopt_own(*new (memory) CallFrame);
|
||||
call_frame->register_count = register_count;
|
||||
for (auto i = 0u; i < register_count; ++i)
|
||||
new (&call_frame->register_values[i]) Value();
|
||||
return call_frame;
|
||||
}
|
||||
|
||||
Interpreter::Interpreter(VM& vm)
|
||||
: m_vm(vm)
|
||||
{
|
||||
|
@ -125,7 +137,7 @@ ThrowCompletionOr<Value> Interpreter::run(Script& script_record, JS::GCPtr<Envir
|
|||
if (result_or_error.value.is_error())
|
||||
result = result_or_error.value.release_error();
|
||||
else
|
||||
result = result_or_error.frame->registers[0];
|
||||
result = result_or_error.frame->registers()[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,9 +366,9 @@ Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable& executa
|
|||
TemporaryChange restore_current_block { m_current_block, entry_point ?: executable.basic_blocks.first() };
|
||||
|
||||
if (in_frame)
|
||||
push_call_frame(in_frame, executable.number_of_registers);
|
||||
push_call_frame(in_frame);
|
||||
else
|
||||
push_call_frame(make<CallFrame>(), executable.number_of_registers);
|
||||
push_call_frame(CallFrame::create(executable.number_of_registers));
|
||||
|
||||
vm().execution_context_stack().last()->executable = &executable;
|
||||
|
||||
|
@ -400,7 +412,7 @@ Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable& executa
|
|||
|
||||
// NOTE: The return value from a called function is put into $0 in the caller context.
|
||||
if (!m_call_frames.is_empty())
|
||||
call_frame().registers[0] = return_value;
|
||||
call_frame().registers()[0] = return_value;
|
||||
|
||||
// At this point we may have already run any queued promise jobs via on_call_stack_emptied,
|
||||
// in which case this is a no-op.
|
||||
|
@ -471,18 +483,17 @@ Realm& Interpreter::realm()
|
|||
return *m_vm.current_realm();
|
||||
}
|
||||
|
||||
void Interpreter::push_call_frame(Variant<NonnullOwnPtr<CallFrame>, CallFrame*> frame, size_t register_count)
|
||||
void Interpreter::push_call_frame(Variant<NonnullOwnPtr<CallFrame>, CallFrame*> frame)
|
||||
{
|
||||
m_call_frames.append(move(frame));
|
||||
this->call_frame().registers.resize(register_count);
|
||||
m_current_call_frame = this->call_frame().registers;
|
||||
m_current_call_frame = this->call_frame().registers();
|
||||
reg(Register::return_value()) = {};
|
||||
}
|
||||
|
||||
Variant<NonnullOwnPtr<CallFrame>, CallFrame*> Interpreter::pop_call_frame()
|
||||
{
|
||||
auto frame = m_call_frames.take_last();
|
||||
m_current_call_frame = m_call_frames.is_empty() ? Span<Value> {} : this->call_frame().registers;
|
||||
m_current_call_frame = m_call_frames.is_empty() ? Span<Value> {} : this->call_frame().registers();
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,9 +20,13 @@ namespace JS::Bytecode {
|
|||
class InstructionStreamIterator;
|
||||
|
||||
struct CallFrame {
|
||||
static NonnullOwnPtr<CallFrame> create(size_t register_count);
|
||||
|
||||
void operator delete(void* ptr) { free(ptr); }
|
||||
|
||||
void visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
for (auto const& value : registers)
|
||||
for (auto const& value : registers())
|
||||
visitor.visit(value);
|
||||
for (auto const& environment : saved_lexical_environments)
|
||||
visitor.visit(environment);
|
||||
|
@ -30,10 +34,16 @@ struct CallFrame {
|
|||
visitor.visit(context.lexical_environment);
|
||||
}
|
||||
}
|
||||
Vector<Value> registers;
|
||||
|
||||
Vector<GCPtr<Environment>> saved_lexical_environments;
|
||||
Vector<UnwindInfo> unwind_contexts;
|
||||
Vector<BasicBlock const*> previously_scheduled_jumps;
|
||||
|
||||
Span<Value> registers() { return { register_values, register_count }; }
|
||||
ReadonlySpan<Value> registers() const { return { register_values, register_count }; }
|
||||
|
||||
size_t register_count { 0 };
|
||||
Value register_values[];
|
||||
};
|
||||
|
||||
class Interpreter {
|
||||
|
@ -101,7 +111,7 @@ private:
|
|||
return const_cast<Interpreter*>(this)->call_frame();
|
||||
}
|
||||
|
||||
void push_call_frame(Variant<NonnullOwnPtr<CallFrame>, CallFrame*>, size_t register_count);
|
||||
void push_call_frame(Variant<NonnullOwnPtr<CallFrame>, CallFrame*>);
|
||||
[[nodiscard]] Variant<NonnullOwnPtr<CallFrame>, CallFrame*> pop_call_frame();
|
||||
|
||||
VM& m_vm;
|
||||
|
|
|
@ -691,7 +691,7 @@ ThrowCompletionOr<Value> perform_eval(VM& vm, Value x, CallerMode strict_caller,
|
|||
if (result_or_error.value.is_error())
|
||||
return result_or_error.value.release_error();
|
||||
|
||||
auto& result = result_or_error.frame->registers[0];
|
||||
auto& result = result_or_error.frame->registers()[0];
|
||||
if (!result.is_empty())
|
||||
eval_result = result;
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace JS {
|
|||
|
||||
JS_DEFINE_ALLOCATOR(AsyncGenerator);
|
||||
|
||||
ThrowCompletionOr<NonnullGCPtr<AsyncGenerator>> AsyncGenerator::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, NonnullOwnPtr<ExecutionContext> execution_context, Bytecode::CallFrame frame)
|
||||
ThrowCompletionOr<NonnullGCPtr<AsyncGenerator>> AsyncGenerator::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, NonnullOwnPtr<ExecutionContext> execution_context, NonnullOwnPtr<Bytecode::CallFrame> frame)
|
||||
{
|
||||
auto& vm = realm.vm();
|
||||
// This is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png)
|
||||
|
@ -45,7 +45,7 @@ void AsyncGenerator::visit_edges(Cell::Visitor& visitor)
|
|||
}
|
||||
visitor.visit(m_generating_function);
|
||||
visitor.visit(m_previous_value);
|
||||
if (m_frame.has_value())
|
||||
if (m_frame)
|
||||
m_frame->visit_edges(visitor);
|
||||
visitor.visit(m_current_promise);
|
||||
}
|
||||
|
@ -192,18 +192,18 @@ void AsyncGenerator::execute(VM& vm, Completion completion)
|
|||
VERIFY(!m_generating_function->bytecode_executable()->basic_blocks.find_if([next_block](auto& block) { return block == next_block; }).is_end());
|
||||
|
||||
Bytecode::CallFrame* frame = nullptr;
|
||||
if (m_frame.has_value())
|
||||
frame = &m_frame.value();
|
||||
if (m_frame)
|
||||
frame = m_frame.ptr();
|
||||
|
||||
if (frame)
|
||||
frame->registers[0] = completion_object;
|
||||
frame->registers()[0] = completion_object;
|
||||
else
|
||||
bytecode_interpreter.accumulator() = completion_object;
|
||||
|
||||
auto next_result = bytecode_interpreter.run_and_return_frame(*m_generating_function->bytecode_executable(), next_block, frame);
|
||||
|
||||
if (!m_frame.has_value())
|
||||
m_frame = move(*next_result.frame);
|
||||
if (!m_frame)
|
||||
m_frame = move(next_result.frame);
|
||||
|
||||
auto result_value = move(next_result.value);
|
||||
if (!result_value.is_throw_completion()) {
|
||||
|
|
|
@ -28,7 +28,7 @@ public:
|
|||
Completed,
|
||||
};
|
||||
|
||||
static ThrowCompletionOr<NonnullGCPtr<AsyncGenerator>> create(Realm&, Value, ECMAScriptFunctionObject*, NonnullOwnPtr<ExecutionContext>, Bytecode::CallFrame);
|
||||
static ThrowCompletionOr<NonnullGCPtr<AsyncGenerator>> create(Realm&, Value, ECMAScriptFunctionObject*, NonnullOwnPtr<ExecutionContext>, NonnullOwnPtr<Bytecode::CallFrame>);
|
||||
|
||||
virtual ~AsyncGenerator() override = default;
|
||||
|
||||
|
@ -60,7 +60,7 @@ private:
|
|||
|
||||
GCPtr<ECMAScriptFunctionObject> m_generating_function;
|
||||
Value m_previous_value;
|
||||
Optional<Bytecode::CallFrame> m_frame;
|
||||
OwnPtr<Bytecode::CallFrame> m_frame;
|
||||
GCPtr<Promise> m_current_promise;
|
||||
};
|
||||
|
||||
|
|
|
@ -704,7 +704,7 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
|
|||
if (value_and_frame.value.is_error())
|
||||
return value_and_frame.value.release_error();
|
||||
// Resulting value is in the accumulator.
|
||||
argument_value = value_and_frame.frame->registers.at(0);
|
||||
argument_value = value_and_frame.frame->registers()[0];
|
||||
} else {
|
||||
argument_value = js_undefined();
|
||||
}
|
||||
|
@ -1225,11 +1225,11 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
|
|||
return { Completion::Type::Return, result.value_or(js_undefined()), {} };
|
||||
|
||||
if (m_kind == FunctionKind::AsyncGenerator) {
|
||||
auto async_generator_object = TRY(AsyncGenerator::create(realm, result, this, vm.running_execution_context().copy(), move(*result_and_frame.frame)));
|
||||
auto async_generator_object = TRY(AsyncGenerator::create(realm, result, this, vm.running_execution_context().copy(), result_and_frame.frame.release_nonnull()));
|
||||
return { Completion::Type::Return, async_generator_object, {} };
|
||||
}
|
||||
|
||||
auto generator_object = TRY(GeneratorObject::create(realm, result, this, vm.running_execution_context().copy(), move(*result_and_frame.frame)));
|
||||
auto generator_object = TRY(GeneratorObject::create(realm, result, this, vm.running_execution_context().copy(), result_and_frame.frame.release_nonnull()));
|
||||
|
||||
// NOTE: Async functions are entirely transformed to generator functions, and wrapped in a custom driver that returns a promise
|
||||
// See AwaitExpression::generate_bytecode() for the transformation.
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace JS {
|
|||
|
||||
JS_DEFINE_ALLOCATOR(GeneratorObject);
|
||||
|
||||
ThrowCompletionOr<NonnullGCPtr<GeneratorObject>> GeneratorObject::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, NonnullOwnPtr<ExecutionContext> execution_context, Bytecode::CallFrame frame)
|
||||
ThrowCompletionOr<NonnullGCPtr<GeneratorObject>> GeneratorObject::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, NonnullOwnPtr<ExecutionContext> execution_context, NonnullOwnPtr<Bytecode::CallFrame> frame)
|
||||
{
|
||||
auto& vm = realm.vm();
|
||||
// This is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png)
|
||||
|
@ -49,7 +49,7 @@ void GeneratorObject::visit_edges(Cell::Visitor& visitor)
|
|||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_generating_function);
|
||||
visitor.visit(m_previous_value);
|
||||
if (m_frame.has_value())
|
||||
if (m_frame)
|
||||
m_frame->visit_edges(visitor);
|
||||
}
|
||||
|
||||
|
@ -113,11 +113,11 @@ ThrowCompletionOr<Value> GeneratorObject::execute(VM& vm, Completion const& comp
|
|||
VERIFY(!m_generating_function->bytecode_executable()->basic_blocks.find_if([next_block](auto& block) { return block == next_block; }).is_end());
|
||||
|
||||
Bytecode::CallFrame* frame = nullptr;
|
||||
if (m_frame.has_value())
|
||||
frame = &m_frame.value();
|
||||
if (m_frame)
|
||||
frame = m_frame;
|
||||
|
||||
if (frame)
|
||||
frame->registers[0] = completion_object;
|
||||
frame->registers()[0] = completion_object;
|
||||
else
|
||||
bytecode_interpreter.accumulator() = completion_object;
|
||||
|
||||
|
@ -125,8 +125,8 @@ ThrowCompletionOr<Value> GeneratorObject::execute(VM& vm, Completion const& comp
|
|||
|
||||
vm.pop_execution_context();
|
||||
|
||||
if (!m_frame.has_value())
|
||||
m_frame = move(*next_result.frame);
|
||||
if (!m_frame)
|
||||
m_frame = move(next_result.frame);
|
||||
|
||||
auto result_value = move(next_result.value);
|
||||
if (result_value.is_throw_completion()) {
|
||||
|
|
|
@ -17,7 +17,7 @@ class GeneratorObject : public Object {
|
|||
JS_DECLARE_ALLOCATOR(GeneratorObject);
|
||||
|
||||
public:
|
||||
static ThrowCompletionOr<NonnullGCPtr<GeneratorObject>> create(Realm&, Value, ECMAScriptFunctionObject*, NonnullOwnPtr<ExecutionContext>, Bytecode::CallFrame);
|
||||
static ThrowCompletionOr<NonnullGCPtr<GeneratorObject>> create(Realm&, Value, ECMAScriptFunctionObject*, NonnullOwnPtr<ExecutionContext>, NonnullOwnPtr<Bytecode::CallFrame>);
|
||||
virtual ~GeneratorObject() override = default;
|
||||
void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
|
@ -43,7 +43,7 @@ private:
|
|||
NonnullOwnPtr<ExecutionContext> m_execution_context;
|
||||
GCPtr<ECMAScriptFunctionObject> m_generating_function;
|
||||
Value m_previous_value;
|
||||
Optional<Bytecode::CallFrame> m_frame;
|
||||
OwnPtr<Bytecode::CallFrame> m_frame;
|
||||
GeneratorState m_generator_state { GeneratorState::SuspendedStart };
|
||||
Optional<StringView> m_generator_brand;
|
||||
};
|
||||
|
|
|
@ -185,7 +185,7 @@ ThrowCompletionOr<Value> perform_shadow_realm_eval(VM& vm, StringView source_tex
|
|||
result = value_and_frame.value.release_error();
|
||||
} else {
|
||||
// Resulting value is in the accumulator.
|
||||
result = value_and_frame.frame->registers.at(0).value_or(js_undefined());
|
||||
result = value_and_frame.frame->registers()[0].value_or(js_undefined());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -285,7 +285,7 @@ ThrowCompletionOr<Value> VM::execute_ast_node(ASTNode const& node)
|
|||
auto result_or_error = bytecode_interpreter().run_and_return_frame(*executable, nullptr);
|
||||
if (result_or_error.value.is_error())
|
||||
return result_or_error.value.release_error();
|
||||
return result_or_error.frame->registers[0];
|
||||
return result_or_error.frame->registers()[0];
|
||||
}
|
||||
|
||||
// 13.15.5.3 Runtime Semantics: PropertyDestructuringAssignmentEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-propertydestructuringassignmentevaluation
|
||||
|
|
|
@ -708,7 +708,7 @@ ThrowCompletionOr<void> SourceTextModule::execute_module(VM& vm, GCPtr<PromiseCa
|
|||
result = value_and_frame.value.release_error();
|
||||
} else {
|
||||
// Resulting value is in the accumulator.
|
||||
result = value_and_frame.frame->registers.at(0).value_or(js_undefined());
|
||||
result = value_and_frame.frame->registers()[0].value_or(js_undefined());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue