mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-05-05 06:32:30 +02:00
LibJS: Flatten Operand to 32-bit index in bytecode instruction stream
While we're in the bytecode compiler, we want to know which type of Operand we're dealing with, but once we've generated the bytecode stream, we only ever need its index. This patch simplifies Operand by removing the aarch64 bitfield hacks and makes it 32-bit on all platforms. We keep 3 type bits in the high bits of the index while compiling, and then zero them out when flattening the final bytecode stream. This makes bytecode more compact on x86_64, and avoids bit twiddling on aarch64. Everyone wins something! When stringifying bytecode for debugging output, we now have an API in Executable that can look at a raw operand index and tell you what type of operand it was, based on known quantities of each type in the stack frame.
This commit is contained in:
committed by
Andreas Kling
parent
3248f5bc59
commit
9f822345bf
Notes:
github-actions[bot]
2025-12-10 03:48:09 +00:00
Author: https://github.com/awesomekling Commit: https://github.com/LadybirdBrowser/ladybird/commit/9f822345bf2 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/7080
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2024, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
@@ -115,4 +115,15 @@ UnrealizedSourceRange Executable::source_range_at(size_t offset) const
|
||||
};
|
||||
}
|
||||
|
||||
Operand Executable::original_operand_from_raw(u32 raw) const
|
||||
{
|
||||
if (raw < number_of_registers)
|
||||
return Operand { Operand::Type::Register, raw };
|
||||
if (raw < local_index_base)
|
||||
return Operand { Operand::Type::Constant, raw - static_cast<u32>(number_of_registers) };
|
||||
if (raw < argument_index_base)
|
||||
return Operand { Operand::Type::Local, raw - static_cast<u32>(local_index_base) };
|
||||
return Operand { Operand::Type::Argument, raw - static_cast<u32>(argument_index_base) };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2024, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <LibGC/WeakInlines.h>
|
||||
#include <LibJS/Bytecode/IdentifierTable.h>
|
||||
#include <LibJS/Bytecode/Label.h>
|
||||
#include <LibJS/Bytecode/Operand.h>
|
||||
#include <LibJS/Bytecode/StringTable.h>
|
||||
#include <LibJS/Export.h>
|
||||
#include <LibJS/Forward.h>
|
||||
@@ -128,6 +129,8 @@ public:
|
||||
|
||||
void dump() const;
|
||||
|
||||
[[nodiscard]] Operand original_operand_from_raw(u32) const;
|
||||
|
||||
private:
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
};
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
|
||||
namespace JS::Bytecode {
|
||||
|
||||
inline ByteString format_operand(StringView name, Operand operand, Bytecode::Executable const& executable)
|
||||
inline ByteString format_operand(StringView name, Operand encoded_operand, Bytecode::Executable const& executable)
|
||||
{
|
||||
StringBuilder builder;
|
||||
if (!name.is_empty())
|
||||
builder.appendff("\033[32m{}\033[0m:", name);
|
||||
auto operand = executable.original_operand_from_raw(encoded_operand.raw());
|
||||
switch (operand.type()) {
|
||||
case Operand::Type::Register:
|
||||
if (operand.index() == Register::this_value().index()) {
|
||||
@@ -30,14 +31,14 @@ inline ByteString format_operand(StringView name, Operand operand, Bytecode::Exe
|
||||
}
|
||||
break;
|
||||
case Operand::Type::Local:
|
||||
builder.appendff("\033[34m{}~{}\033[0m", executable.local_variable_names[operand.index() - executable.local_index_base].name, operand.index() - executable.local_index_base);
|
||||
builder.appendff("\033[34m{}~{}\033[0m", executable.local_variable_names[operand.index()].name, operand.index());
|
||||
break;
|
||||
case Operand::Type::Argument:
|
||||
builder.appendff("\033[34marg{}\033[0m", operand.index() - executable.argument_index_base);
|
||||
builder.appendff("\033[34marg{}\033[0m", operand.index());
|
||||
break;
|
||||
case Operand::Type::Constant: {
|
||||
builder.append("\033[36m"sv);
|
||||
auto value = executable.constants[operand.index() - executable.number_of_registers];
|
||||
auto value = executable.constants[operand.index()];
|
||||
if (value.is_special_empty_value())
|
||||
builder.append("<Empty>"sv);
|
||||
else if (value.is_boolean())
|
||||
|
||||
@@ -79,8 +79,7 @@ size_t Instruction::length() const
|
||||
}
|
||||
|
||||
Operand::Operand(Register reg)
|
||||
: m_type(Type::Register)
|
||||
, m_index(reg.index())
|
||||
: Operand(Type::Register, reg.index())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -92,12 +92,12 @@ Interpreter::~Interpreter() = default;
|
||||
|
||||
ALWAYS_INLINE Value Interpreter::get(Operand op) const
|
||||
{
|
||||
return m_running_execution_context->registers_and_constants_and_locals_and_arguments()[op.index()];
|
||||
return m_running_execution_context->registers_and_constants_and_locals_and_arguments()[op.raw()];
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void Interpreter::set(Operand op, Value value)
|
||||
{
|
||||
m_running_execution_context->registers_and_constants_and_locals_and_arguments()[op.index()] = value;
|
||||
m_running_execution_context->registers_and_constants_and_locals_and_arguments()[op.raw()] = value;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE Value Interpreter::do_yield(Value value, Optional<Label> continuation)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2024-2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
@@ -13,12 +13,7 @@ namespace JS::Bytecode {
|
||||
|
||||
class Operand {
|
||||
public:
|
||||
enum class Type
|
||||
#if ARCH(AARCH64)
|
||||
: u8
|
||||
#endif
|
||||
{
|
||||
Invalid,
|
||||
enum class Type {
|
||||
Register,
|
||||
Local,
|
||||
Constant,
|
||||
@@ -28,46 +23,40 @@ public:
|
||||
[[nodiscard]] bool operator==(Operand const&) const = default;
|
||||
|
||||
explicit Operand(Type type, u32 index)
|
||||
: m_type(type)
|
||||
, m_index(index)
|
||||
: m_raw(to_underlying(type) << 29 | index)
|
||||
{
|
||||
}
|
||||
|
||||
enum class ShouldMakeInvalid { Indeed };
|
||||
explicit Operand(ShouldMakeInvalid)
|
||||
: m_raw(0xffffffffu)
|
||||
{
|
||||
#if ARCH(AARCH64)
|
||||
VERIFY((index & 0x3fffffff) == index);
|
||||
#endif
|
||||
}
|
||||
|
||||
explicit Operand(Register);
|
||||
|
||||
[[nodiscard]] bool is_register() const { return m_type == Type::Register; }
|
||||
[[nodiscard]] bool is_local() const { return m_type == Type::Local; }
|
||||
[[nodiscard]] bool is_constant() const { return m_type == Type::Constant; }
|
||||
[[nodiscard]] bool is_invalid() const { return m_raw == 0xffffffffu; }
|
||||
[[nodiscard]] bool is_register() const { return type() == Type::Register; }
|
||||
[[nodiscard]] bool is_local() const { return type() == Type::Local; }
|
||||
[[nodiscard]] bool is_constant() const { return type() == Type::Constant; }
|
||||
|
||||
[[nodiscard]] Type type() const { return m_type; }
|
||||
[[nodiscard]] u32 index() const { return m_index; }
|
||||
[[nodiscard]] Type type() const { return static_cast<Type>((m_raw & 0xe0000000u) >> 29); }
|
||||
[[nodiscard]] u32 index() const { return m_raw & 0x1fffffff; }
|
||||
|
||||
[[nodiscard]] u32 raw() const { return m_raw; }
|
||||
|
||||
[[nodiscard]] Register as_register() const;
|
||||
|
||||
void offset_index_by(u32 offset) { m_index += offset; }
|
||||
void offset_index_by(u32 offset)
|
||||
{
|
||||
m_raw &= 0x1fffffff;
|
||||
m_raw += offset;
|
||||
}
|
||||
|
||||
private:
|
||||
// NOTE: aarch64 gets much faster with bitfields here, while x86_64 gets slower.
|
||||
// Because this type is absolutely essential to the interpreter, we allow
|
||||
// ourselves this little ifdef.
|
||||
#if ARCH(AARCH64)
|
||||
Type m_type : 3 {};
|
||||
u32 m_index : 29 { 0 };
|
||||
#else
|
||||
Type m_type { Type::Invalid };
|
||||
u32 m_index { 0 };
|
||||
#endif
|
||||
u32 m_raw { 0 };
|
||||
};
|
||||
|
||||
#if ARCH(AARCH64)
|
||||
static_assert(sizeof(Operand) == 4);
|
||||
#else
|
||||
static_assert(sizeof(Operand) == 8);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
namespace AK {
|
||||
@@ -131,12 +120,12 @@ public:
|
||||
|
||||
void clear()
|
||||
{
|
||||
m_value = JS::Bytecode::Operand { JS::Bytecode::Operand::Type::Invalid, 0 };
|
||||
m_value = JS::Bytecode::Operand { JS::Bytecode::Operand::ShouldMakeInvalid::Indeed };
|
||||
}
|
||||
|
||||
[[nodiscard]] bool has_value() const
|
||||
{
|
||||
return m_value.type() != JS::Bytecode::Operand::Type::Invalid;
|
||||
return !m_value.is_invalid();
|
||||
}
|
||||
|
||||
[[nodiscard]] JS::Bytecode::Operand& value() &
|
||||
@@ -165,7 +154,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
JS::Bytecode::Operand m_value { JS::Bytecode::Operand::Type::Invalid, 0 };
|
||||
JS::Bytecode::Operand m_value { JS::Bytecode::Operand::ShouldMakeInvalid::Indeed };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ ScopedOperandImpl::~ScopedOperandImpl()
|
||||
|
||||
Register Operand::as_register() const
|
||||
{
|
||||
return Register { m_index };
|
||||
return Register { index() };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user