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:
Andreas Kling 2025-11-20 22:14:50 +01:00 committed by Andreas Kling
parent 84443e1de6
commit 003589db2d
Notes: github-actions[bot] 2025-11-21 08:47:47 +00:00
12 changed files with 1975 additions and 4198 deletions

View file

@ -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 {};
},