mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-07 21:59:54 +00:00
LibJS: Generate C++ bytecode instruction classes from a definition file
This commit adds a new Bytecode.def file that describes all the LibJS bytecode instructions. From this, we are able to generate the full declarations for all C++ bytecode instruction classes, as well as their serialization code. Note that some of the bytecode compiler was updated since instructions no longer have default constructor arguments. The big immediate benefit here is that we lose a couple thousand lines of hand-written C++ code. Going forward, this also allows us to do more tooling for the bytecode VM, now that we have an authoritative description of its instructions. Key things to know about: - Instructions can inherit from one another. At the moment, everything simply inherits from the base "Instruction". - @terminator means the instruction terminates a basic block. - @nothrow means the instruction cannot throw. This affects how the interpreter interacts with it. - Variable-length instructions are automatically supported. Just put an array of something as the last field of the instruction. - The m_length field is magical. If present, it will be populated with the full length of the instruction. This is used for variable-length instructions.
This commit is contained in:
parent
84443e1de6
commit
003589db2d
Notes:
github-actions[bot]
2025-11-21 08:47:47 +00:00
Author: https://github.com/awesomekling
Commit: 003589db2d
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6888
12 changed files with 1975 additions and 4198 deletions
|
|
@ -495,7 +495,7 @@ static Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> arguments_to_arr
|
|||
{
|
||||
auto dst = generator.allocate_register();
|
||||
if (arguments.is_empty()) {
|
||||
generator.emit<Bytecode::Op::NewArray>(dst);
|
||||
generator.emit<Bytecode::Op::NewArray>(dst, ReadonlySpan<ScopedOperand> {});
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
|
@ -512,9 +512,9 @@ static Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> arguments_to_arr
|
|||
}
|
||||
|
||||
if (first_spread.index() != 0)
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(args.size(), dst, args);
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(args.size(), dst, args.span());
|
||||
else
|
||||
generator.emit<Bytecode::Op::NewArray>(dst);
|
||||
generator.emit<Bytecode::Op::NewArray>(dst, ReadonlySpan<ScopedOperand> {});
|
||||
|
||||
if (first_spread != arguments.end()) {
|
||||
for (auto it = first_spread; it != arguments.end(); ++it) {
|
||||
|
|
@ -1034,7 +1034,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ForStatement::generate_
|
|||
if (identifier.is_local())
|
||||
return;
|
||||
auto index = generator.intern_identifier(identifier.string());
|
||||
generator.emit<Bytecode::Op::CreateVariable>(index, Bytecode::Op::EnvironmentMode::Lexical, is_const);
|
||||
generator.emit<Bytecode::Op::CreateVariable>(index, Bytecode::Op::EnvironmentMode::Lexical, is_const, false, false);
|
||||
if (!is_const) {
|
||||
per_iteration_bindings.append(index);
|
||||
}
|
||||
|
|
@ -1067,7 +1067,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ForStatement::generate_
|
|||
generator.begin_variable_scope();
|
||||
|
||||
for (size_t i = 0; i < per_iteration_bindings.size(); ++i) {
|
||||
generator.emit<Bytecode::Op::CreateVariable>(per_iteration_bindings[i], Bytecode::Op::EnvironmentMode::Lexical, false);
|
||||
generator.emit<Bytecode::Op::CreateVariable>(per_iteration_bindings[i], Bytecode::Op::EnvironmentMode::Lexical, false, false, false);
|
||||
generator.emit<Bytecode::Op::InitializeLexicalBinding>(per_iteration_bindings[i], registers[i]);
|
||||
}
|
||||
};
|
||||
|
|
@ -1206,7 +1206,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ArrayExpression::genera
|
|||
Bytecode::Generator::SourceLocationScope scope(generator, *this);
|
||||
if (m_elements.is_empty()) {
|
||||
auto dst = choose_dst(generator, preferred_dst);
|
||||
generator.emit<Bytecode::Op::NewArray>(dst);
|
||||
generator.emit<Bytecode::Op::NewArray>(dst, ReadonlySpan<ScopedOperand> {});
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
|
@ -1242,7 +1242,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ArrayExpression::genera
|
|||
if (first_spread.index() != 0) {
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(args.size(), dst, args);
|
||||
} else {
|
||||
generator.emit<Bytecode::Op::NewArray>(dst);
|
||||
generator.emit<Bytecode::Op::NewArray>(dst, ReadonlySpan<ScopedOperand> {});
|
||||
}
|
||||
|
||||
if (first_spread != m_elements.end()) {
|
||||
|
|
@ -1288,7 +1288,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> FunctionExpression::gen
|
|||
generator.begin_variable_scope();
|
||||
|
||||
name_identifier = generator.intern_identifier(name());
|
||||
generator.emit<Bytecode::Op::CreateVariable>(*name_identifier, Bytecode::Op::EnvironmentMode::Lexical, true);
|
||||
generator.emit<Bytecode::Op::CreateVariable>(*name_identifier, Bytecode::Op::EnvironmentMode::Lexical, true, false, false);
|
||||
}
|
||||
|
||||
auto new_function = choose_dst(generator, preferred_dst);
|
||||
|
|
@ -1437,7 +1437,7 @@ static Bytecode::CodeGenerationErrorOr<void> generate_array_binding_pattern_byte
|
|||
auto iterator_object = generator.allocate_register();
|
||||
auto iterator_next_method = generator.allocate_register();
|
||||
auto iterator_done_property = generator.allocate_register();
|
||||
generator.emit<Bytecode::Op::GetIterator>(iterator_object, iterator_next_method, iterator_done_property, input_array);
|
||||
generator.emit<Bytecode::Op::GetIterator>(iterator_object, iterator_next_method, iterator_done_property, input_array, IteratorHint::Sync);
|
||||
bool first = true;
|
||||
|
||||
auto assign_value_to_alias = [&](auto& alias, ScopedOperand value) {
|
||||
|
|
@ -1485,7 +1485,7 @@ static Bytecode::CodeGenerationErrorOr<void> generate_array_binding_pattern_byte
|
|||
value = generator.allocate_register();
|
||||
|
||||
generator.switch_to_basic_block(if_exhausted_block);
|
||||
generator.emit<Bytecode::Op::NewArray>(value);
|
||||
generator.emit<Bytecode::Op::NewArray>(value, ReadonlySpan<ScopedOperand> {});
|
||||
generator.emit<Bytecode::Op::Jump>(Bytecode::Label { continuation_block });
|
||||
|
||||
generator.switch_to_basic_block(if_not_exhausted_block);
|
||||
|
|
@ -1801,32 +1801,32 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> CallExpression::generat
|
|||
dst,
|
||||
callee,
|
||||
this_value,
|
||||
argument_operands,
|
||||
builtin.value(),
|
||||
expression_string_index);
|
||||
expression_string_index,
|
||||
argument_operands);
|
||||
} else if (call_type == Op::CallType::Construct) {
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::CallConstruct>(
|
||||
argument_operands.size(),
|
||||
dst,
|
||||
callee,
|
||||
argument_operands,
|
||||
expression_string_index);
|
||||
expression_string_index,
|
||||
argument_operands);
|
||||
} else if (call_type == Op::CallType::DirectEval) {
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::CallDirectEval>(
|
||||
argument_operands.size(),
|
||||
dst,
|
||||
callee,
|
||||
this_value,
|
||||
argument_operands,
|
||||
expression_string_index);
|
||||
expression_string_index,
|
||||
argument_operands);
|
||||
} else {
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::Call>(
|
||||
argument_operands.size(),
|
||||
dst,
|
||||
callee,
|
||||
this_value,
|
||||
argument_operands,
|
||||
expression_string_index);
|
||||
expression_string_index,
|
||||
argument_operands);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2023,7 +2023,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
|
|||
auto array = generator.allocate_register();
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(1, array, ReadonlySpan<ScopedOperand> { &received_completion_value, 1 });
|
||||
auto inner_result = generator.allocate_register();
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(inner_result, next_method, iterator, array);
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(inner_result, next_method, iterator, array, OptionalNone {});
|
||||
|
||||
// ii. If generatorKind is async, set innerResult to ? Await(innerResult).
|
||||
if (generator.is_in_async_generator_function()) {
|
||||
|
|
@ -2112,7 +2112,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
|
|||
// 1. Let innerResult be ? Call(throw, iterator, « received.[[Value]] »).
|
||||
auto received_value_array = generator.allocate_register();
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(1, received_value_array, ReadonlySpan<ScopedOperand> { &received_completion_value, 1 });
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(inner_result, throw_method, iterator, received_value_array);
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(inner_result, throw_method, iterator, received_value_array, OptionalNone {});
|
||||
|
||||
// 2. If generatorKind is async, set innerResult to ? Await(innerResult).
|
||||
if (generator.is_in_async_generator_function()) {
|
||||
|
|
@ -2209,7 +2209,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
|
|||
auto call_array = generator.allocate_register();
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(1, call_array, ReadonlySpan<ScopedOperand> { &received_completion_value, 1 });
|
||||
auto inner_return_result = generator.allocate_register();
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(inner_return_result, return_method, iterator, call_array);
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(inner_return_result, return_method, iterator, call_array, OptionalNone {});
|
||||
|
||||
// v. If generatorKind is async, set innerReturnResult to ? Await(innerReturnResult).
|
||||
if (generator.is_in_async_generator_function()) {
|
||||
|
|
@ -2508,7 +2508,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> TaggedTemplateLiteral::
|
|||
|
||||
auto strings_array = generator.allocate_register();
|
||||
if (string_regs.is_empty()) {
|
||||
generator.emit<Bytecode::Op::NewArray>(strings_array);
|
||||
generator.emit<Bytecode::Op::NewArray>(strings_array, ReadonlySpan<ScopedOperand> {});
|
||||
} else {
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(string_regs.size(), strings_array, string_regs);
|
||||
}
|
||||
|
|
@ -2532,7 +2532,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> TaggedTemplateLiteral::
|
|||
|
||||
auto raw_strings_array = generator.allocate_register();
|
||||
if (raw_string_regs.is_empty()) {
|
||||
generator.emit<Bytecode::Op::NewArray>(raw_strings_array);
|
||||
generator.emit<Bytecode::Op::NewArray>(raw_strings_array, ReadonlySpan<ScopedOperand> {});
|
||||
} else {
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(raw_string_regs.size(), raw_strings_array, raw_string_regs);
|
||||
}
|
||||
|
|
@ -2543,10 +2543,10 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> TaggedTemplateLiteral::
|
|||
if (!argument_regs.is_empty())
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(argument_regs.size(), arguments, argument_regs);
|
||||
else
|
||||
generator.emit<Bytecode::Op::NewArray>(arguments);
|
||||
generator.emit<Bytecode::Op::NewArray>(arguments, ReadonlySpan<ScopedOperand> {});
|
||||
|
||||
auto dst = choose_dst(generator, preferred_dst);
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(dst, tag, this_value, arguments);
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(dst, tag, this_value, arguments, OptionalNone {});
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
|
@ -2665,7 +2665,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> TryStatement::generate_
|
|||
generator.begin_variable_scope();
|
||||
did_create_variable_scope_for_catch_clause = true;
|
||||
auto parameter_identifier = generator.intern_identifier(parameter->string());
|
||||
generator.emit<Bytecode::Op::CreateVariable>(parameter_identifier, Bytecode::Op::EnvironmentMode::Lexical, false);
|
||||
generator.emit<Bytecode::Op::CreateVariable>(parameter_identifier, Bytecode::Op::EnvironmentMode::Lexical, false, false, false);
|
||||
generator.emit<Bytecode::Op::InitializeLexicalBinding>(parameter_identifier, caught_value);
|
||||
}
|
||||
return {};
|
||||
|
|
@ -2683,7 +2683,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> TryStatement::generate_
|
|||
if (identifier.is_local())
|
||||
return;
|
||||
auto parameter_identifier = generator.intern_identifier(identifier.string());
|
||||
generator.emit<Bytecode::Op::CreateVariable>(parameter_identifier, Bytecode::Op::EnvironmentMode::Lexical, false);
|
||||
generator.emit<Bytecode::Op::CreateVariable>(parameter_identifier, Bytecode::Op::EnvironmentMode::Lexical, false, false, false);
|
||||
}));
|
||||
|
||||
TRY(binding_pattern->generate_bytecode(generator, Bytecode::Op::BindingInitializationMode::Initialize, caught_value));
|
||||
|
|
@ -2885,12 +2885,12 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ClassDeclaration::gener
|
|||
Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ClassExpression::generate_bytecode_with_lhs_name(Bytecode::Generator& generator, Optional<Bytecode::IdentifierTableIndex> lhs_name, Optional<ScopedOperand> preferred_dst) const
|
||||
{
|
||||
// NOTE: Step 2 is not a part of NewClass instruction because it is assumed to be done before super class expression evaluation
|
||||
generator.emit<Bytecode::Op::CreateLexicalEnvironment>();
|
||||
generator.emit<Bytecode::Op::CreateLexicalEnvironment>(OptionalNone {}, 0);
|
||||
|
||||
if (has_name() || !lhs_name.has_value()) {
|
||||
// NOTE: Step 3.a is not a part of NewClass instruction because it is assumed to be done before super class expression evaluation
|
||||
auto interned_index = generator.intern_identifier(name());
|
||||
generator.emit<Bytecode::Op::CreateVariable>(interned_index, Bytecode::Op::EnvironmentMode::Lexical, true);
|
||||
generator.emit<Bytecode::Op::CreateVariable>(interned_index, Bytecode::Op::EnvironmentMode::Lexical, true, false, false);
|
||||
}
|
||||
|
||||
Optional<ScopedOperand> super_class;
|
||||
|
|
@ -3098,7 +3098,7 @@ static Bytecode::CodeGenerationErrorOr<ForInOfHeadEvaluationResult> for_in_of_he
|
|||
return;
|
||||
// i. Perform ! newEnv.CreateMutableBinding(name, false).
|
||||
auto interned_identifier = generator.intern_identifier(identifier.string());
|
||||
generator.emit<Bytecode::Op::CreateVariable>(interned_identifier, Bytecode::Op::EnvironmentMode::Lexical, false);
|
||||
generator.emit<Bytecode::Op::CreateVariable>(interned_identifier, Bytecode::Op::EnvironmentMode::Lexical, false, false, false);
|
||||
}));
|
||||
// d. Set the running execution context's LexicalEnvironment to newEnv.
|
||||
// NOTE: Done by CreateLexicalEnvironment.
|
||||
|
|
@ -3298,7 +3298,7 @@ static Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> for_in_of_body_e
|
|||
// b. Else,
|
||||
else {
|
||||
// i. Perform ! environment.CreateMutableBinding(name, false).
|
||||
generator.emit<Bytecode::Op::CreateVariable>(interned_identifier, Bytecode::Op::EnvironmentMode::Lexical, false);
|
||||
generator.emit<Bytecode::Op::CreateVariable>(interned_identifier, Bytecode::Op::EnvironmentMode::Lexical, false, false, false);
|
||||
}
|
||||
}));
|
||||
// 3. Return unused.
|
||||
|
|
@ -3512,7 +3512,7 @@ static Bytecode::CodeGenerationErrorOr<void> generate_optional_chain(Bytecode::G
|
|||
TRY(reference.visit(
|
||||
[&](OptionalChain::Call const& call) -> Bytecode::CodeGenerationErrorOr<void> {
|
||||
auto arguments = TRY(arguments_to_array_for_call(generator, call.arguments)).value();
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(current_value, current_value, current_base, arguments);
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(current_value, current_value, current_base, arguments, OptionalNone {});
|
||||
generator.emit_mov(current_base, generator.add_constant(js_undefined()));
|
||||
return {};
|
||||
},
|
||||
|
|
|
|||
1064
Libraries/LibJS/Bytecode/Bytecode.def
Normal file
1064
Libraries/LibJS/Bytecode/Bytecode.def
Normal file
File diff suppressed because it is too large
Load diff
92
Libraries/LibJS/Bytecode/FormatOperand.h
Normal file
92
Libraries/LibJS/Bytecode/FormatOperand.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteString.h>
|
||||
#include <LibJS/Bytecode/Executable.h>
|
||||
#include <LibJS/Bytecode/Operand.h>
|
||||
#include <LibJS/Bytecode/Register.h>
|
||||
#include <LibJS/Runtime/BigInt.h>
|
||||
#include <LibJS/Runtime/PrimitiveString.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
|
||||
namespace JS::Bytecode {
|
||||
|
||||
inline ByteString format_operand(StringView name, Operand operand, Bytecode::Executable const& executable)
|
||||
{
|
||||
StringBuilder builder;
|
||||
if (!name.is_empty())
|
||||
builder.appendff("\033[32m{}\033[0m:", name);
|
||||
switch (operand.type()) {
|
||||
case Operand::Type::Register:
|
||||
if (operand.index() == Register::this_value().index()) {
|
||||
builder.appendff("\033[33mthis\033[0m");
|
||||
} else {
|
||||
builder.appendff("\033[33mreg{}\033[0m", operand.index());
|
||||
}
|
||||
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);
|
||||
break;
|
||||
case Operand::Type::Argument:
|
||||
builder.appendff("\033[34marg{}\033[0m", operand.index() - executable.argument_index_base);
|
||||
break;
|
||||
case Operand::Type::Constant: {
|
||||
builder.append("\033[36m"sv);
|
||||
auto value = executable.constants[operand.index() - executable.number_of_registers];
|
||||
if (value.is_special_empty_value())
|
||||
builder.append("<Empty>"sv);
|
||||
else if (value.is_boolean())
|
||||
builder.appendff("Bool({})", value.as_bool() ? "true"sv : "false"sv);
|
||||
else if (value.is_int32())
|
||||
builder.appendff("Int32({})", value.as_i32());
|
||||
else if (value.is_double())
|
||||
builder.appendff("Double({})", value.as_double());
|
||||
else if (value.is_bigint())
|
||||
builder.appendff("BigInt({})", MUST(value.as_bigint().to_string()));
|
||||
else if (value.is_string())
|
||||
builder.appendff("String(\"{}\")", value.as_string().utf8_string_view());
|
||||
else if (value.is_undefined())
|
||||
builder.append("Undefined"sv);
|
||||
else if (value.is_null())
|
||||
builder.append("Null"sv);
|
||||
else
|
||||
builder.appendff("Value: {}", value);
|
||||
builder.append("\033[0m"sv);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
return builder.to_byte_string();
|
||||
}
|
||||
|
||||
inline ByteString format_operand_list(StringView name, ReadonlySpan<Operand> operands, Bytecode::Executable const& executable)
|
||||
{
|
||||
StringBuilder builder;
|
||||
if (!name.is_empty())
|
||||
builder.appendff("\033[32m{}\033[0m:[", name);
|
||||
for (size_t i = 0; i < operands.size(); ++i) {
|
||||
if (i != 0)
|
||||
builder.append(", "sv);
|
||||
builder.appendff("{}", format_operand(""sv, operands[i], executable));
|
||||
}
|
||||
builder.append("]"sv);
|
||||
return builder.to_byte_string();
|
||||
}
|
||||
|
||||
inline ByteString format_value_list(StringView name, ReadonlySpan<Value> values)
|
||||
{
|
||||
StringBuilder builder;
|
||||
if (!name.is_empty())
|
||||
builder.appendff("\033[32m{}\033[0m:[", name);
|
||||
builder.join(", "sv, values);
|
||||
builder.append("]"sv);
|
||||
return builder.to_byte_string();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -41,13 +41,13 @@ CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(E
|
|||
}
|
||||
}
|
||||
if (has_non_local_parameters)
|
||||
emit<Op::CreateLexicalEnvironment>();
|
||||
emit<Op::CreateLexicalEnvironment>(OptionalNone {}, 0);
|
||||
}
|
||||
|
||||
for (auto const& parameter_name : function.shared_data().m_parameter_names) {
|
||||
if (parameter_name.value == SharedFunctionInstanceData::ParameterIsLocal::No) {
|
||||
auto id = intern_identifier(parameter_name.key);
|
||||
emit<Op::CreateVariable>(id, Op::EnvironmentMode::Lexical, false);
|
||||
emit<Op::CreateVariable>(id, Op::EnvironmentMode::Lexical, false, false, false);
|
||||
if (function.shared_data().m_has_duplicates) {
|
||||
emit<Op::InitializeLexicalBinding>(id, add_constant(js_undefined()));
|
||||
}
|
||||
|
|
@ -61,9 +61,9 @@ CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(E
|
|||
dst = local(Identifier::Local::variable(local_var_index.value()));
|
||||
|
||||
if (function.is_strict_mode() || !function.has_simple_parameter_list()) {
|
||||
emit<Op::CreateArguments>(dst, Op::CreateArguments::Kind::Unmapped, function.is_strict_mode());
|
||||
emit<Op::CreateArguments>(dst, Op::ArgumentsKind::Unmapped, function.is_strict_mode());
|
||||
} else {
|
||||
emit<Op::CreateArguments>(dst, Op::CreateArguments::Kind::Mapped, function.is_strict_mode());
|
||||
emit<Op::CreateArguments>(dst, Op::ArgumentsKind::Mapped, function.is_strict_mode());
|
||||
}
|
||||
|
||||
if (local_var_index.has_value())
|
||||
|
|
@ -123,7 +123,7 @@ CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(E
|
|||
emit<Op::Mov>(local(id.local_index()), add_constant(js_undefined()));
|
||||
} else {
|
||||
auto intern_id = intern_identifier(id.string());
|
||||
emit<Op::CreateVariable>(intern_id, Op::EnvironmentMode::Var, false);
|
||||
emit<Op::CreateVariable>(intern_id, Op::EnvironmentMode::Var, false, false, false);
|
||||
emit<Op::InitializeVariableBinding>(intern_id, add_constant(js_undefined()));
|
||||
}
|
||||
}
|
||||
|
|
@ -161,7 +161,7 @@ CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(E
|
|||
emit<Op::Mov>(local(id.local_index()), initial_value);
|
||||
} else {
|
||||
auto intern_id = intern_identifier(id.string());
|
||||
emit<Op::CreateVariable>(intern_id, Op::EnvironmentMode::Var, false);
|
||||
emit<Op::CreateVariable>(intern_id, Op::EnvironmentMode::Var, false, false, false);
|
||||
emit<Op::InitializeVariableBinding>(intern_id, initial_value);
|
||||
}
|
||||
}
|
||||
|
|
@ -171,7 +171,7 @@ CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(E
|
|||
if (!function.is_strict_mode() && scope_body) {
|
||||
for (auto const& function_name : function.shared_data().m_function_names_to_initialize_binding) {
|
||||
auto intern_id = intern_identifier(function_name);
|
||||
emit<Op::CreateVariable>(intern_id, Op::EnvironmentMode::Var, false);
|
||||
emit<Op::CreateVariable>(intern_id, Op::EnvironmentMode::Var, false, false, false);
|
||||
emit<Op::InitializeVariableBinding>(intern_id, add_constant(js_undefined()));
|
||||
}
|
||||
}
|
||||
|
|
@ -203,11 +203,11 @@ CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(E
|
|||
auto const& identifier = *declaration.name_identifier();
|
||||
if (identifier.is_local()) {
|
||||
auto local_index = identifier.local_index();
|
||||
emit<Op::NewFunction>(local(local_index), declaration, OptionalNone {});
|
||||
emit<Op::NewFunction>(local(local_index), declaration, OptionalNone {}, OptionalNone {});
|
||||
set_local_initialized(local_index);
|
||||
} else {
|
||||
auto function = allocate_register();
|
||||
emit<Op::NewFunction>(function, declaration, OptionalNone {});
|
||||
emit<Op::NewFunction>(function, declaration, OptionalNone {}, OptionalNone {});
|
||||
emit<Op::SetVariableBinding>(intern_identifier(declaration.name()), function);
|
||||
}
|
||||
}
|
||||
|
|
@ -583,7 +583,7 @@ bool Generator::emit_block_declaration_instantiation(ScopeNode const& scope_node
|
|||
return false;
|
||||
|
||||
auto environment = allocate_register();
|
||||
emit<Bytecode::Op::CreateLexicalEnvironment>(environment);
|
||||
emit<Bytecode::Op::CreateLexicalEnvironment>(environment, 0);
|
||||
start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
||||
|
||||
MUST(scope_node.for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
|
||||
|
|
@ -618,7 +618,7 @@ bool Generator::emit_block_declaration_instantiation(ScopeNode const& scope_node
|
|||
|
||||
// ii. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv.
|
||||
auto fo = allocate_register();
|
||||
emit<Bytecode::Op::NewFunction>(fo, function_declaration, OptionalNone {});
|
||||
emit<Bytecode::Op::NewFunction>(fo, function_declaration, OptionalNone {}, OptionalNone {});
|
||||
|
||||
// iii. Perform ! env.InitializeBinding(fn, fo). NOTE: This step is replaced in section B.3.2.6.
|
||||
if (function_declaration.name_identifier()->is_local()) {
|
||||
|
|
@ -640,7 +640,7 @@ bool Generator::emit_block_declaration_instantiation(ScopeNode const& scope_node
|
|||
void Generator::begin_variable_scope()
|
||||
{
|
||||
start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
||||
emit<Bytecode::Op::CreateLexicalEnvironment>();
|
||||
emit<Bytecode::Op::CreateLexicalEnvironment>(OptionalNone {}, 0);
|
||||
}
|
||||
|
||||
void Generator::end_variable_scope()
|
||||
|
|
@ -1144,7 +1144,7 @@ void Generator::pop_home_object()
|
|||
void Generator::emit_new_function(ScopedOperand dst, FunctionExpression const& function_node, Optional<IdentifierTableIndex> lhs_name)
|
||||
{
|
||||
if (m_home_objects.is_empty()) {
|
||||
emit<Op::NewFunction>(dst, function_node, lhs_name);
|
||||
emit<Op::NewFunction>(dst, function_node, lhs_name, OptionalNone {});
|
||||
} else {
|
||||
emit<Op::NewFunction>(dst, function_node, lhs_name, m_home_objects.last());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <LibJS/Bytecode/IdentifierTable.h>
|
||||
#include <LibJS/Bytecode/Label.h>
|
||||
#include <LibJS/Bytecode/Op.h>
|
||||
#include <LibJS/Bytecode/PutKind.h>
|
||||
#include <LibJS/Bytecode/Register.h>
|
||||
#include <LibJS/Bytecode/StringTable.h>
|
||||
#include <LibJS/Forward.h>
|
||||
|
|
@ -310,7 +311,7 @@ public:
|
|||
if constexpr (IsSame<OpType, Op::Return>)
|
||||
emit<Op::Return>(value);
|
||||
else
|
||||
emit<Op::Yield>(nullptr, value);
|
||||
emit<Op::Yield>(OptionalNone {}, value);
|
||||
}
|
||||
|
||||
void start_boundary(BlockBoundaryType type) { m_boundaries.append(type); }
|
||||
|
|
|
|||
|
|
@ -10,174 +10,61 @@
|
|||
#include <AK/Function.h>
|
||||
#include <AK/Span.h>
|
||||
#include <LibJS/Bytecode/Executable.h>
|
||||
#include <LibJS/Bytecode/PutKind.h>
|
||||
#include <LibJS/Bytecode/OpCodes.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/SourceRange.h>
|
||||
|
||||
#define ENUMERATE_BYTECODE_OPS(O) \
|
||||
O(Add) \
|
||||
O(AddPrivateName) \
|
||||
O(ArrayAppend) \
|
||||
O(AsyncIteratorClose) \
|
||||
O(Await) \
|
||||
O(BitwiseAnd) \
|
||||
O(BitwiseNot) \
|
||||
O(BitwiseOr) \
|
||||
O(BitwiseXor) \
|
||||
O(Call) \
|
||||
O(CallBuiltin) \
|
||||
O(CallConstruct) \
|
||||
O(CallConstructWithArgumentArray) \
|
||||
O(CallDirectEval) \
|
||||
O(CallDirectEvalWithArgumentArray) \
|
||||
O(CallWithArgumentArray) \
|
||||
O(Catch) \
|
||||
O(ConcatString) \
|
||||
O(ContinuePendingUnwind) \
|
||||
O(CopyObjectExcludingProperties) \
|
||||
O(CreateArguments) \
|
||||
O(CreateLexicalEnvironment) \
|
||||
O(CreateImmutableBinding) \
|
||||
O(CreateMutableBinding) \
|
||||
O(CreatePrivateEnvironment) \
|
||||
O(CreateRestParams) \
|
||||
O(CreateVariable) \
|
||||
O(CreateVariableEnvironment) \
|
||||
O(Decrement) \
|
||||
O(DeleteById) \
|
||||
O(DeleteByIdWithThis) \
|
||||
O(DeleteByValue) \
|
||||
O(DeleteByValueWithThis) \
|
||||
O(DeleteVariable) \
|
||||
O(Div) \
|
||||
O(Dump) \
|
||||
O(End) \
|
||||
O(EnterObjectEnvironment) \
|
||||
O(EnterUnwindContext) \
|
||||
O(Exp) \
|
||||
O(GetById) \
|
||||
O(GetByIdWithThis) \
|
||||
O(GetByValue) \
|
||||
O(GetByValueWithThis) \
|
||||
O(GetCalleeAndThisFromEnvironment) \
|
||||
O(GetCompletionFields) \
|
||||
O(GetGlobal) \
|
||||
O(GetImportMeta) \
|
||||
O(GetIterator) \
|
||||
O(GetLength) \
|
||||
O(GetLengthWithThis) \
|
||||
O(GetMethod) \
|
||||
O(GetNewTarget) \
|
||||
O(GetObjectPropertyIterator) \
|
||||
O(GetPrivateById) \
|
||||
O(GetBinding) \
|
||||
O(GetInitializedBinding) \
|
||||
O(GreaterThan) \
|
||||
O(GreaterThanEquals) \
|
||||
O(HasPrivateId) \
|
||||
O(ImportCall) \
|
||||
O(In) \
|
||||
O(Increment) \
|
||||
O(InitializeLexicalBinding) \
|
||||
O(InitializeVariableBinding) \
|
||||
O(InstanceOf) \
|
||||
O(IteratorClose) \
|
||||
O(IteratorNext) \
|
||||
O(IteratorNextUnpack) \
|
||||
O(IteratorToArray) \
|
||||
O(Jump) \
|
||||
O(JumpFalse) \
|
||||
O(JumpGreaterThan) \
|
||||
O(JumpGreaterThanEquals) \
|
||||
O(JumpIf) \
|
||||
O(JumpLessThan) \
|
||||
O(JumpLessThanEquals) \
|
||||
O(JumpLooselyEquals) \
|
||||
O(JumpLooselyInequals) \
|
||||
O(JumpNullish) \
|
||||
O(JumpStrictlyEquals) \
|
||||
O(JumpStrictlyInequals) \
|
||||
O(JumpTrue) \
|
||||
O(JumpUndefined) \
|
||||
O(LeaveFinally) \
|
||||
O(LeaveLexicalEnvironment) \
|
||||
O(LeavePrivateEnvironment) \
|
||||
O(LeaveUnwindContext) \
|
||||
O(LeftShift) \
|
||||
O(LessThan) \
|
||||
O(LessThanEquals) \
|
||||
O(LooselyEquals) \
|
||||
O(LooselyInequals) \
|
||||
O(Mod) \
|
||||
O(Mov) \
|
||||
O(Mul) \
|
||||
O(NewArray) \
|
||||
O(NewClass) \
|
||||
O(NewFunction) \
|
||||
O(NewObject) \
|
||||
O(NewPrimitiveArray) \
|
||||
O(NewRegExp) \
|
||||
O(NewTypeError) \
|
||||
O(Not) \
|
||||
O(PrepareYield) \
|
||||
O(PostfixDecrement) \
|
||||
O(PostfixIncrement) \
|
||||
O(PutNormalById) \
|
||||
O(PutOwnById) \
|
||||
O(PutGetterById) \
|
||||
O(PutSetterById) \
|
||||
O(PutPrototypeById) \
|
||||
O(PutNormalByNumericId) \
|
||||
O(PutOwnByNumericId) \
|
||||
O(PutGetterByNumericId) \
|
||||
O(PutSetterByNumericId) \
|
||||
O(PutPrototypeByNumericId) \
|
||||
O(PutNormalByIdWithThis) \
|
||||
O(PutOwnByIdWithThis) \
|
||||
O(PutGetterByIdWithThis) \
|
||||
O(PutSetterByIdWithThis) \
|
||||
O(PutPrototypeByIdWithThis) \
|
||||
O(PutNormalByNumericIdWithThis) \
|
||||
O(PutOwnByNumericIdWithThis) \
|
||||
O(PutGetterByNumericIdWithThis) \
|
||||
O(PutSetterByNumericIdWithThis) \
|
||||
O(PutPrototypeByNumericIdWithThis) \
|
||||
O(PutBySpread) \
|
||||
O(PutNormalByValue) \
|
||||
O(PutOwnByValue) \
|
||||
O(PutGetterByValue) \
|
||||
O(PutSetterByValue) \
|
||||
O(PutPrototypeByValue) \
|
||||
O(PutNormalByValueWithThis) \
|
||||
O(PutOwnByValueWithThis) \
|
||||
O(PutGetterByValueWithThis) \
|
||||
O(PutSetterByValueWithThis) \
|
||||
O(PutPrototypeByValueWithThis) \
|
||||
O(PutPrivateById) \
|
||||
O(ResolveSuperBase) \
|
||||
O(ResolveThisBinding) \
|
||||
O(RestoreScheduledJump) \
|
||||
O(Return) \
|
||||
O(RightShift) \
|
||||
O(ScheduleJump) \
|
||||
O(SetCompletionType) \
|
||||
O(SetGlobal) \
|
||||
O(SetLexicalBinding) \
|
||||
O(SetVariableBinding) \
|
||||
O(StrictlyEquals) \
|
||||
O(StrictlyInequals) \
|
||||
O(Sub) \
|
||||
O(SuperCallWithArgumentArray) \
|
||||
O(Throw) \
|
||||
O(ThrowIfNotObject) \
|
||||
O(ThrowIfNullish) \
|
||||
O(ThrowIfTDZ) \
|
||||
O(Typeof) \
|
||||
O(TypeofBinding) \
|
||||
O(UnaryMinus) \
|
||||
O(UnaryPlus) \
|
||||
O(UnsignedRightShift) \
|
||||
O(Yield)
|
||||
namespace JS::Bytecode::Op {
|
||||
|
||||
#define JS_ENUMERATE_COMMON_BINARY_OPS_WITHOUT_FAST_PATH(O) \
|
||||
O(Exp, exp) \
|
||||
O(Mod, mod) \
|
||||
O(In, in) \
|
||||
O(InstanceOf, instance_of) \
|
||||
O(LooselyInequals, loosely_inequals) \
|
||||
O(LooselyEquals, loosely_equals) \
|
||||
O(StrictlyInequals, strict_inequals) \
|
||||
O(StrictlyEquals, strict_equals)
|
||||
|
||||
#define JS_ENUMERATE_COMMON_UNARY_OPS(O) \
|
||||
O(BitwiseNot, bitwise_not) \
|
||||
O(Not, not_) \
|
||||
O(UnaryPlus, unary_plus) \
|
||||
O(UnaryMinus, unary_minus) \
|
||||
O(Typeof, typeof_)
|
||||
|
||||
#define JS_ENUMERATE_COMPARISON_OPS(X) \
|
||||
X(LessThan, less_than, <) \
|
||||
X(LessThanEquals, less_than_equals, <=) \
|
||||
X(GreaterThan, greater_than, >) \
|
||||
X(GreaterThanEquals, greater_than_equals, >=) \
|
||||
X(LooselyEquals, loosely_equals, ==) \
|
||||
X(LooselyInequals, loosely_inequals, !=) \
|
||||
X(StrictlyEquals, strict_equals, ==) \
|
||||
X(StrictlyInequals, strict_inequals, !=)
|
||||
|
||||
enum class EnvironmentMode {
|
||||
Lexical,
|
||||
Var,
|
||||
};
|
||||
|
||||
enum class BindingInitializationMode {
|
||||
Initialize,
|
||||
Set,
|
||||
};
|
||||
|
||||
enum class CallType {
|
||||
Call,
|
||||
Construct,
|
||||
DirectEval,
|
||||
};
|
||||
|
||||
enum class ArgumentsKind {
|
||||
Mapped,
|
||||
Unmapped,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace JS::Bytecode {
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +1,5 @@
|
|||
include(libjs_generators)
|
||||
|
||||
set(SOURCES
|
||||
AST.cpp
|
||||
Bytecode/ASTCodegen.cpp
|
||||
|
|
@ -265,6 +267,10 @@ set(SOURCES
|
|||
Token.cpp
|
||||
)
|
||||
|
||||
generate_bytecode_def_derived()
|
||||
|
||||
set(GENERATED_SOURCES Bytecode/Op.cpp)
|
||||
|
||||
ladybird_lib(LibJS js EXPLICIT_SYMBOL_EXPORT)
|
||||
target_link_libraries(LibJS PRIVATE LibCore LibCrypto LibFileSystem LibRegex LibSyntax LibGC)
|
||||
|
||||
|
|
|
|||
12
Meta/CMake/libjs_generators.cmake
Normal file
12
Meta/CMake/libjs_generators.cmake
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
function(generate_bytecode_def_derived)
|
||||
set(LIBJS_INPUT_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
invoke_py_generator(
|
||||
"Op.cpp"
|
||||
"generate-libjs-bytecode-def-derived.py"
|
||||
"${LIBJS_INPUT_FOLDER}/Bytecode/Bytecode.def"
|
||||
"Bytecode/Op.h"
|
||||
"Bytecode/Op.cpp"
|
||||
EXTRA_HEADER "Bytecode/OpCodes.h"
|
||||
arguments -i "${LIBJS_INPUT_FOLDER}/Bytecode/Bytecode.def"
|
||||
)
|
||||
endfunction()
|
||||
596
Meta/generate-libjs-bytecode-def-derived.py
Normal file
596
Meta/generate-libjs-bytecode-def-derived.py
Normal file
|
|
@ -0,0 +1,596 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from libjs_bytecode_def import Field
|
||||
from libjs_bytecode_def import OpDef
|
||||
from libjs_bytecode_def import parse_bytecode_def
|
||||
|
||||
|
||||
def mname_to_param(name: str) -> str:
|
||||
if name.startswith("m_") and len(name) > 2:
|
||||
return name[2:]
|
||||
return name
|
||||
|
||||
|
||||
def getter_name_for_field(field_name: str) -> str:
|
||||
return mname_to_param(field_name)
|
||||
|
||||
|
||||
def is_operand_type(t: str) -> bool:
|
||||
t = t.strip()
|
||||
return t == "Operand" or t == "Optional<Operand>"
|
||||
|
||||
|
||||
def is_optional_operand_type(t: str) -> bool:
|
||||
return t.strip() == "Optional<Operand>"
|
||||
|
||||
|
||||
def is_label_type(t: str) -> bool:
|
||||
t = t.strip()
|
||||
return t == "Label" or t == "Optional<Label>"
|
||||
|
||||
|
||||
def is_optional_label_type(t: str) -> bool:
|
||||
return t.strip() == "Optional<Label>"
|
||||
|
||||
|
||||
def is_value_type(t: str) -> bool:
|
||||
t = t.strip()
|
||||
return t == "Value" or t == "Optional<Value>"
|
||||
|
||||
|
||||
def find_count_field_name(op: OpDef, array_field: Field) -> Optional[str]:
|
||||
"""
|
||||
Heuristic: look for a u32/size_t field matching
|
||||
- <name>_count
|
||||
- if name ends with 's', also <name_without_s>_count
|
||||
"""
|
||||
candidates = [f"{array_field.name}_count"]
|
||||
|
||||
if array_field.name.endswith("s"):
|
||||
base = array_field.name[:-1]
|
||||
candidates.append(f"{base}_count")
|
||||
|
||||
for cand in candidates:
|
||||
for f in op.fields:
|
||||
if f.name == cand and not f.is_array and f.type.strip() in ("u32", "size_t"):
|
||||
return cand
|
||||
return None
|
||||
|
||||
|
||||
def get_count_field_name_or_die(op: OpDef, array_field: Field) -> str:
|
||||
name = find_count_field_name(op, array_field)
|
||||
if name is None:
|
||||
raise RuntimeError(f"No count field (u32/size_t) found for array field '{array_field.name}' in op '{op.name}'")
|
||||
return name
|
||||
|
||||
|
||||
def generate_enum_macro(ops: List[OpDef]) -> str:
|
||||
filtered = [op for op in ops if op.name != "Instruction"]
|
||||
lines = []
|
||||
lines.append("#define ENUMERATE_BYTECODE_OPS(O) \\")
|
||||
for i, op in enumerate(filtered):
|
||||
last = i == len(filtered) - 1
|
||||
if last:
|
||||
lines.append(f" O({op.name})")
|
||||
else:
|
||||
lines.append(f" O({op.name}) \\")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_visit_operands(op: OpDef) -> Optional[str]:
|
||||
has_any_operand = any(is_operand_type(f.type) for f in op.fields)
|
||||
if not has_any_operand:
|
||||
return None
|
||||
|
||||
lines: List[str] = []
|
||||
lines.append(" void visit_operands_impl(Function<void(Operand&)> visitor)")
|
||||
lines.append(" {")
|
||||
|
||||
for f in op.fields:
|
||||
t = f.type.strip()
|
||||
if not is_operand_type(t):
|
||||
continue
|
||||
|
||||
if not f.is_array:
|
||||
if is_optional_operand_type(t):
|
||||
lines.append(f" if ({f.name}.has_value())")
|
||||
lines.append(f" visitor({f.name}.value());")
|
||||
else:
|
||||
lines.append(f" visitor({f.name});")
|
||||
else:
|
||||
count_name = get_count_field_name_or_die(op, f)
|
||||
|
||||
if is_optional_operand_type(t):
|
||||
lines.append(f" for (size_t i = 0; i < {count_name}; ++i) {{")
|
||||
lines.append(f" if ({f.name}[i].has_value())")
|
||||
lines.append(f" visitor({f.name}[i].value());")
|
||||
lines.append(" }")
|
||||
else:
|
||||
lines.append(f" for (size_t i = 0; i < {count_name}; ++i)")
|
||||
lines.append(f" visitor({f.name}[i]);")
|
||||
|
||||
lines.append(" }")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_visit_labels(op: OpDef) -> Optional[str]:
|
||||
has_any_label = any(is_label_type(f.type) for f in op.fields)
|
||||
if not has_any_label:
|
||||
return None
|
||||
|
||||
lines: List[str] = []
|
||||
lines.append(" void visit_labels_impl(Function<void(Label&)> visitor)")
|
||||
lines.append(" {")
|
||||
|
||||
for f in op.fields:
|
||||
t = f.type.strip()
|
||||
if not is_label_type(t):
|
||||
continue
|
||||
|
||||
if not f.is_array:
|
||||
if is_optional_label_type(t):
|
||||
lines.append(f" if ({f.name}.has_value())")
|
||||
lines.append(f" visitor({f.name}.value());")
|
||||
else:
|
||||
lines.append(f" visitor({f.name});")
|
||||
else:
|
||||
count_name = get_count_field_name_or_die(op, f)
|
||||
|
||||
if is_optional_label_type(t):
|
||||
lines.append(f" for (size_t i = 0; i < {count_name}; ++i) {{")
|
||||
lines.append(f" if ({f.name}[i].has_value())")
|
||||
lines.append(f" visitor({f.name}[i].value());")
|
||||
lines.append(" }")
|
||||
else:
|
||||
lines.append(f" for (size_t i = 0; i < {count_name}; ++i)")
|
||||
lines.append(f" visitor({f.name}[i]);")
|
||||
|
||||
lines.append(" }")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_getters(op: OpDef) -> List[str]:
|
||||
lines: List[str] = []
|
||||
for f in op.fields:
|
||||
gname = getter_name_for_field(f.name)
|
||||
if f.is_array:
|
||||
count_name = get_count_field_name_or_die(op, f)
|
||||
rettype = f"ReadonlySpan<{f.type}>"
|
||||
lines.append(
|
||||
f" {rettype} {gname}() const {{ return ReadonlySpan<{f.type}> {{ {f.name}, {count_name} }}; }}"
|
||||
)
|
||||
else:
|
||||
lines.append(f" auto const& {gname}() const {{ return {f.name}; }}")
|
||||
return lines
|
||||
|
||||
|
||||
def execute_return_type(op: OpDef) -> str:
|
||||
return "void" if op.is_nothrow else "ThrowCompletionOr<void>"
|
||||
|
||||
|
||||
def generate_class(op: OpDef) -> str:
|
||||
if op.name == "Instruction":
|
||||
return ""
|
||||
|
||||
base = op.base or "Instruction"
|
||||
lines: List[str] = []
|
||||
|
||||
lines.append(f"class {op.name} final : public {base} {{")
|
||||
lines.append("public:")
|
||||
|
||||
arrays: List[Field] = [f for f in op.fields if f.is_array]
|
||||
has_array = len(arrays) > 0
|
||||
has_m_length = any(f.name == "m_length" for f in op.fields)
|
||||
|
||||
if has_array:
|
||||
lines.append(" static constexpr bool IsVariableLength = true;")
|
||||
if has_m_length:
|
||||
lines.append(" size_t length_impl() const { return m_length; }")
|
||||
if op.is_terminator:
|
||||
lines.append(" static constexpr bool IsTerminator = true;")
|
||||
|
||||
# Map arrays -> count fields (error if missing)
|
||||
array_to_count: dict[str, str] = {}
|
||||
count_to_array_param: dict[str, str] = {}
|
||||
for af in arrays:
|
||||
count_name = get_count_field_name_or_die(op, af)
|
||||
array_to_count[af.name] = count_name
|
||||
count_to_array_param[count_name] = mname_to_param(af.name)
|
||||
|
||||
count_fields = set(count_to_array_param.keys())
|
||||
|
||||
# ctor params: scalars first, then spans
|
||||
ctor_params: List[str] = []
|
||||
for f in op.fields:
|
||||
if f.is_array:
|
||||
continue
|
||||
if f.type.strip() == "EnvironmentCoordinate":
|
||||
continue
|
||||
if f.name in count_fields:
|
||||
continue
|
||||
if f.name == "m_length":
|
||||
# synthesized, don't take as parameter
|
||||
continue
|
||||
ctor_params.append(f"{f.type} {mname_to_param(f.name)}")
|
||||
|
||||
span_param_for_array: dict[str, str] = {}
|
||||
for af in arrays:
|
||||
span_param = mname_to_param(af.name)
|
||||
span_param_for_array[af.name] = span_param
|
||||
|
||||
elem_t = af.type.strip()
|
||||
if elem_t == "Operand":
|
||||
span_elem_type = "ScopedOperand"
|
||||
elif elem_t == "Optional<Operand>":
|
||||
span_elem_type = "Optional<ScopedOperand>"
|
||||
else:
|
||||
span_elem_type = af.type
|
||||
|
||||
ctor_params.append(f"ReadonlySpan<{span_elem_type}> {span_param}")
|
||||
|
||||
if ctor_params:
|
||||
lines.append(f" {op.name}({', '.join(ctor_params)})")
|
||||
else:
|
||||
lines.append(f" {op.name}()")
|
||||
|
||||
# initializer list
|
||||
init_entries: List[str] = [f"{base}(Type::{op.name})"]
|
||||
for f in op.fields:
|
||||
if f.is_array:
|
||||
continue
|
||||
if f.type.strip() == "EnvironmentCoordinate":
|
||||
continue
|
||||
if f.name in count_fields:
|
||||
span_param = count_to_array_param[f.name]
|
||||
init_entries.append(f"{f.name}({span_param}.size())")
|
||||
continue
|
||||
if f.name == "m_length":
|
||||
# choose the first array field as the one that defines the tail
|
||||
array_for_length: Optional[Field] = arrays[0] if arrays else None
|
||||
if array_for_length is not None:
|
||||
span_param = span_param_for_array.get(array_for_length.name)
|
||||
elem_t = array_for_length.type.strip()
|
||||
if span_param is not None:
|
||||
init_entries.append(
|
||||
"m_length(round_up_to_power_of_two("
|
||||
"alignof(void*), sizeof(*this) + sizeof("
|
||||
f"{elem_t}) * {span_param}.size()))"
|
||||
)
|
||||
else:
|
||||
init_entries.append("m_length(0)")
|
||||
else:
|
||||
init_entries.append("m_length(0)")
|
||||
continue
|
||||
|
||||
init_entries.append(f"{f.name}({mname_to_param(f.name)})")
|
||||
|
||||
if init_entries:
|
||||
lines.append(f" : {init_entries[0]}")
|
||||
for entry in init_entries[1:]:
|
||||
lines.append(f" , {entry}")
|
||||
lines.append(" {")
|
||||
# copy spans into trailing arrays
|
||||
for af in arrays:
|
||||
span_param = span_param_for_array[af.name]
|
||||
count_name = array_to_count[af.name]
|
||||
elem_t = af.type.strip()
|
||||
if elem_t == "Optional<Operand>":
|
||||
lines.append(f" for (size_t i = 0; i < {span_param}.size(); ++i) {{")
|
||||
lines.append(f" if ({span_param}[i].has_value())")
|
||||
lines.append(f" {af.name}[i] = {span_param}[i].value();")
|
||||
lines.append(" else")
|
||||
lines.append(f" {af.name}[i] = {{}};")
|
||||
lines.append(" }")
|
||||
else:
|
||||
lines.append(f" for (size_t i = 0; i < {span_param}.size(); ++i)")
|
||||
lines.append(f" {af.name}[i] = {span_param}[i];")
|
||||
lines.append(" }")
|
||||
lines.append("")
|
||||
|
||||
ret_type = execute_return_type(op)
|
||||
lines.append(f" {ret_type} execute_impl(Bytecode::Interpreter&) const;")
|
||||
lines.append(" ByteString to_byte_string_impl(Bytecode::Executable const&) const;")
|
||||
|
||||
visit_operands = generate_visit_operands(op)
|
||||
if visit_operands:
|
||||
lines.append(visit_operands)
|
||||
|
||||
visit_labels = generate_visit_labels(op)
|
||||
if visit_labels:
|
||||
lines.append(visit_labels)
|
||||
|
||||
getters = generate_getters(op)
|
||||
if getters:
|
||||
lines.append("")
|
||||
lines.extend(getters)
|
||||
|
||||
lines.append("")
|
||||
lines.append("private:")
|
||||
for f in op.fields:
|
||||
if f.is_array:
|
||||
lines.append(f" {f.type} {f.name}[];")
|
||||
else:
|
||||
if f.type.strip() == "EnvironmentCoordinate":
|
||||
lines.append(f" mutable {f.type} {f.name};")
|
||||
else:
|
||||
lines.append(f" {f.type} {f.name};")
|
||||
|
||||
lines.append("};")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_op_namespace_body(ops: List[OpDef]) -> str:
|
||||
lines: List[str] = []
|
||||
lines.append("namespace JS::Bytecode::Op {")
|
||||
lines.append("")
|
||||
for op in ops:
|
||||
cls = generate_class(op)
|
||||
if cls:
|
||||
lines.append(cls)
|
||||
lines.append("")
|
||||
lines.append("} // namespace JS::Bytecode::Op")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
NUMERIC_TYPES = {
|
||||
"u8",
|
||||
"u16",
|
||||
"u32",
|
||||
"u64",
|
||||
"i8",
|
||||
"i16",
|
||||
"i32",
|
||||
"i64",
|
||||
"int",
|
||||
"unsigned",
|
||||
"unsigned int",
|
||||
"size_t",
|
||||
}
|
||||
|
||||
|
||||
def generate_to_byte_string_impl(op: OpDef) -> str:
|
||||
if op.name == "Instruction":
|
||||
return ""
|
||||
|
||||
lines: List[str] = []
|
||||
lines.append(
|
||||
f"ByteString {op.name}::to_byte_string_impl([[maybe_unused]] Bytecode::Executable const& executable) const"
|
||||
)
|
||||
lines.append("{")
|
||||
lines.append(" StringBuilder builder;")
|
||||
lines.append(f' builder.append("{op.name}"sv);')
|
||||
lines.append("")
|
||||
lines.append(" bool first = true;")
|
||||
lines.append(" [[maybe_unused]] auto append_piece = [&](auto const& piece) {")
|
||||
lines.append(" if (first) {")
|
||||
lines.append(" builder.append(' ');")
|
||||
lines.append(" first = false;")
|
||||
lines.append(" } else {")
|
||||
lines.append(' builder.append(", "sv);')
|
||||
lines.append(" }")
|
||||
lines.append(" builder.append(piece);")
|
||||
lines.append(" };")
|
||||
lines.append("")
|
||||
|
||||
arrays: List[Field] = [f for f in op.fields if f.is_array]
|
||||
array_to_count = {af.name: get_count_field_name_or_die(op, af) for af in arrays}
|
||||
count_fields = set(array_to_count.values())
|
||||
|
||||
for f in op.fields:
|
||||
# never print m_length
|
||||
if f.name == "m_length":
|
||||
continue
|
||||
|
||||
t = f.type.strip()
|
||||
label = getter_name_for_field(f.name)
|
||||
|
||||
if f.is_array:
|
||||
count_name = array_to_count[f.name]
|
||||
|
||||
if t == "Operand":
|
||||
lines.append(f" if ({count_name} != 0)")
|
||||
lines.append(
|
||||
f' append_piece(format_operand_list("{label}"sv, {{ {f.name}, {count_name} }}, executable));'
|
||||
)
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "Optional<Operand>":
|
||||
lines.append(f" if ({count_name} != 0) {{")
|
||||
lines.append(" StringBuilder list_builder;")
|
||||
lines.append(f' list_builder.appendff("{{}}:[", "{label}"sv);')
|
||||
lines.append(" bool first_elem = true;")
|
||||
lines.append(f" for (size_t i = 0; i < {count_name}; ++i) {{")
|
||||
lines.append(f" if (!{f.name}[i].has_value())")
|
||||
lines.append(" continue;")
|
||||
lines.append(" if (!first_elem)")
|
||||
lines.append(' list_builder.append(", "sv);')
|
||||
lines.append(" first_elem = false;")
|
||||
lines.append(
|
||||
f' list_builder.append(format_operand("{label}"sv, {f.name}[i].value(), executable));'
|
||||
)
|
||||
lines.append(" }")
|
||||
lines.append(" list_builder.append(']');")
|
||||
lines.append(" append_piece(list_builder.to_byte_string());")
|
||||
lines.append(" }")
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "Value":
|
||||
lines.append(f" if ({count_name} != 0)")
|
||||
lines.append(
|
||||
f' append_piece(format_value_list("{label}"sv, ReadonlySpan<Value> {{ {f.name}, {count_name} }}));'
|
||||
)
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
# other array types not printed
|
||||
continue
|
||||
|
||||
if t == "Operand":
|
||||
lines.append(f' append_piece(format_operand("{label}"sv, {f.name}, executable));')
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "Optional<Operand>":
|
||||
lines.append(f" if ({f.name}.has_value())")
|
||||
lines.append(f' append_piece(format_operand("{label}"sv, {f.name}.value(), executable));')
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "IdentifierTableIndex":
|
||||
lines.append(f" append_piece(executable.identifier_table->get({f.name}));")
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "Optional<IdentifierTableIndex>":
|
||||
lines.append(f" if ({f.name}.has_value())")
|
||||
lines.append(f" append_piece(executable.identifier_table->get({f.name}.value()));")
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "StringTableIndex":
|
||||
lines.append(f" append_piece(executable.get_string({f.name}));")
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "Optional<StringTableIndex>":
|
||||
lines.append(f" if ({f.name}.has_value())")
|
||||
lines.append(f" append_piece(executable.get_string({f.name}.value()));")
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if is_value_type(t):
|
||||
# not printed for now
|
||||
continue
|
||||
|
||||
if t == "bool":
|
||||
lines.append(f' append_piece(ByteString::formatted("{label}:{{}}", {f.name}));')
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t in NUMERIC_TYPES and f.name not in count_fields:
|
||||
lines.append(f' append_piece(ByteString::formatted("{label}:{{}}", {f.name}));')
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
# other types (enums, refs, etc.) skipped
|
||||
|
||||
lines.append(" return builder.to_byte_string();")
|
||||
lines.append("}")
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_op_cpp_body(ops: List[OpDef]) -> str:
|
||||
lines: List[str] = []
|
||||
lines.append("#include <AK/StringBuilder.h>")
|
||||
lines.append("#include <AK/StringView.h>")
|
||||
lines.append("#include <LibJS/Bytecode/FormatOperand.h>")
|
||||
lines.append("#include <LibJS/Bytecode/Op.h>")
|
||||
lines.append("")
|
||||
lines.append("namespace JS::Bytecode::Op {")
|
||||
lines.append("")
|
||||
|
||||
for op in ops:
|
||||
impl = generate_to_byte_string_impl(op)
|
||||
if impl:
|
||||
lines.append(impl)
|
||||
|
||||
lines.append("} // namespace JS::Bytecode::Op")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_opcodes_h(ops: List[OpDef]) -> str:
|
||||
macro = generate_enum_macro(ops)
|
||||
lines: List[str] = []
|
||||
lines.append("#pragma once")
|
||||
lines.append("")
|
||||
lines.append(macro)
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_op_h(ops: List[OpDef]) -> str:
|
||||
includes = """#pragma once
|
||||
|
||||
#include <AK/Span.h>
|
||||
#include <AK/StdLibExtras.h>
|
||||
#include <LibJS/Bytecode/Builtins.h>
|
||||
#include <LibJS/Bytecode/IdentifierTable.h>
|
||||
#include <LibJS/Bytecode/Instruction.h>
|
||||
#include <LibJS/Bytecode/Label.h>
|
||||
#include <LibJS/Bytecode/Operand.h>
|
||||
#include <LibJS/Bytecode/RegexTable.h>
|
||||
#include <LibJS/Bytecode/Register.h>
|
||||
#include <LibJS/Bytecode/ScopedOperand.h>
|
||||
#include <LibJS/Bytecode/StringTable.h>
|
||||
#include <LibJS/Runtime/BigInt.h>
|
||||
#include <LibJS/Runtime/Environment.h>
|
||||
#include <LibJS/Runtime/Iterator.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
"""
|
||||
body = generate_op_namespace_body(ops)
|
||||
return includes + "\n" + body + "\n"
|
||||
|
||||
|
||||
def usage(prog: str) -> None:
|
||||
print(
|
||||
f"Usage: {prog} -c path/to/Op.cpp -h path/to/Op.h -x path/to/OpCodes.h -i path/to/Bytecode.def",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
|
||||
def main(argv: List[str]) -> None:
|
||||
c_path = None
|
||||
h_path = None
|
||||
x_path = None
|
||||
def_path = None
|
||||
|
||||
i = 1
|
||||
while i < len(argv):
|
||||
arg = argv[i]
|
||||
if arg == "-c" and i + 1 < len(argv):
|
||||
c_path = argv[i + 1]
|
||||
i += 2
|
||||
elif arg == "-h" and i + 1 < len(argv):
|
||||
h_path = argv[i + 1]
|
||||
i += 2
|
||||
elif arg == "-x" and i + 1 < len(argv):
|
||||
x_path = argv[i + 1]
|
||||
i += 2
|
||||
elif arg == "-i" and i + 1 < len(argv):
|
||||
def_path = argv[i + 1]
|
||||
i += 2
|
||||
else:
|
||||
usage(argv[0])
|
||||
sys.exit(1)
|
||||
|
||||
if not (c_path and h_path and x_path and def_path):
|
||||
usage(argv[0])
|
||||
sys.exit(1)
|
||||
|
||||
ops = parse_bytecode_def(def_path)
|
||||
|
||||
op_h = generate_op_h(ops)
|
||||
op_cpp = generate_op_cpp_body(ops)
|
||||
opcodes_h = generate_opcodes_h(ops)
|
||||
|
||||
with open(h_path, "w", encoding="utf-8") as f:
|
||||
f.write(op_h)
|
||||
|
||||
with open(c_path, "w", encoding="utf-8") as f:
|
||||
f.write(op_cpp)
|
||||
|
||||
with open(x_path, "w", encoding="utf-8") as f:
|
||||
f.write(opcodes_h)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
||||
97
Meta/libjs_bytecode_def.py
Normal file
97
Meta/libjs_bytecode_def.py
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import field
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class Field:
|
||||
name: str
|
||||
type: str
|
||||
is_array: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class OpDef:
|
||||
name: str
|
||||
base: str
|
||||
fields: List[Field] = field(default_factory=list)
|
||||
is_terminator: bool = False # @terminator
|
||||
is_nothrow: bool = False # @nothrow
|
||||
|
||||
|
||||
def parse_bytecode_def(path: str) -> List[OpDef]:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
ops: List[OpDef] = []
|
||||
current: Optional[OpDef] = None
|
||||
in_op = False
|
||||
|
||||
for raw_line in lines:
|
||||
stripped = raw_line.strip()
|
||||
if not stripped:
|
||||
continue
|
||||
if stripped.startswith("//") or stripped.startswith("#"):
|
||||
continue
|
||||
|
||||
if stripped.startswith("op "):
|
||||
if in_op:
|
||||
raise RuntimeError("Nested op blocks are not allowed")
|
||||
in_op = True
|
||||
|
||||
rest = stripped[len("op ") :].strip()
|
||||
if "<" in rest:
|
||||
name_part, base_part = rest.split("<", 1)
|
||||
name = name_part.strip()
|
||||
base = base_part.strip()
|
||||
else:
|
||||
name = rest.strip()
|
||||
base = "Instruction"
|
||||
|
||||
current = OpDef(name=name, base=base)
|
||||
continue
|
||||
|
||||
if stripped == "endop":
|
||||
if not in_op or current is None:
|
||||
raise RuntimeError("endop without corresponding op")
|
||||
ops.append(current)
|
||||
current = None
|
||||
in_op = False
|
||||
continue
|
||||
|
||||
if not in_op:
|
||||
continue
|
||||
|
||||
if stripped.startswith("@"):
|
||||
if stripped == "@terminator":
|
||||
current.is_terminator = True
|
||||
elif stripped == "@nothrow":
|
||||
current.is_nothrow = True
|
||||
continue
|
||||
|
||||
if ":" not in stripped:
|
||||
raise RuntimeError(f"Malformed field line: {stripped!r}")
|
||||
lhs, rhs = stripped.split(":", 1)
|
||||
field_name = lhs.strip()
|
||||
field_type = rhs.strip()
|
||||
is_array = False
|
||||
if field_type.endswith("[]"):
|
||||
is_array = True
|
||||
field_type = field_type[:-2].strip()
|
||||
current.fields.append(Field(name=field_name, type=field_type, is_array=is_array))
|
||||
|
||||
if in_op or current is not None:
|
||||
raise RuntimeError("Unclosed op block at end of file")
|
||||
|
||||
return ops
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Field",
|
||||
"OpDef",
|
||||
"parse_bytecode_def",
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue