mirror of
https://github.com/RGBCube/serenity
synced 2025-05-20 18:15:07 +00:00
LibJS/Bytecode: Implement break/continue labels
This is done by keeping track of all the labels that apply to a given break/continue scope alongside their bytecode target. When a break/continue with a label is generated, we scan from the most inner scope to the most outer scope looking for the label, performing any necessary unwinds on the way. Once the label is found, it is then jumped to.
This commit is contained in:
parent
455c0b7794
commit
c0fadfb9b7
4 changed files with 218 additions and 30 deletions
|
@ -618,7 +618,80 @@ Bytecode::CodeGenerationErrorOr<void> AssignmentExpression::generate_bytecode(By
|
|||
return {};
|
||||
}
|
||||
|
||||
// 14.13.3 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-labelled-statements-runtime-semantics-evaluation
|
||||
// LabelledStatement : LabelIdentifier : LabelledItem
|
||||
Bytecode::CodeGenerationErrorOr<void> LabelledStatement::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{
|
||||
// Return ? LabelledEvaluation of this LabelledStatement with argument « ».
|
||||
return generate_labelled_evaluation(generator, {});
|
||||
}
|
||||
|
||||
// 14.13.4 Runtime Semantics: LabelledEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-labelledevaluation
|
||||
// LabelledStatement : LabelIdentifier : LabelledItem
|
||||
Bytecode::CodeGenerationErrorOr<void> LabelledStatement::generate_labelled_evaluation(Bytecode::Generator& generator, Vector<FlyString> const& label_set) const
|
||||
{
|
||||
// Convert the m_labelled_item NNRP to a reference early so we don't have to do it every single time we want to use it.
|
||||
auto const& labelled_item = *m_labelled_item;
|
||||
|
||||
// 1. Let label be the StringValue of LabelIdentifier.
|
||||
// NOTE: Not necessary, this is m_label.
|
||||
|
||||
// 2. Let newLabelSet be the list-concatenation of labelSet and « label ».
|
||||
// FIXME: Avoid copy here.
|
||||
auto new_label_set = label_set;
|
||||
new_label_set.append(m_label);
|
||||
|
||||
// 3. Let stmtResult be LabelledEvaluation of LabelledItem with argument newLabelSet.
|
||||
// NOTE: stmtResult will be in the accumulator after running the generated bytecode.
|
||||
if (is<IterationStatement>(labelled_item)) {
|
||||
auto const& iteration_statement = static_cast<IterationStatement const&>(labelled_item);
|
||||
TRY(iteration_statement.generate_labelled_evaluation(generator, new_label_set));
|
||||
} else if (is<SwitchStatement>(labelled_item)) {
|
||||
auto const& switch_statement = static_cast<SwitchStatement const&>(labelled_item);
|
||||
TRY(switch_statement.generate_labelled_evaluation(generator, new_label_set));
|
||||
} else if (is<LabelledStatement>(labelled_item)) {
|
||||
auto const& labelled_statement = static_cast<LabelledStatement const&>(labelled_item);
|
||||
TRY(labelled_statement.generate_labelled_evaluation(generator, new_label_set));
|
||||
} else {
|
||||
auto& labelled_break_block = generator.make_block();
|
||||
|
||||
// NOTE: We do not need a continuable scope as `continue;` is not allowed outside of iteration statements, throwing a SyntaxError in the parser.
|
||||
generator.begin_breakable_scope(Bytecode::Label { labelled_break_block }, new_label_set);
|
||||
TRY(labelled_item.generate_bytecode(generator));
|
||||
generator.end_breakable_scope();
|
||||
|
||||
if (!generator.is_current_block_terminated()) {
|
||||
generator.emit<Bytecode::Op::Jump>().set_targets(
|
||||
Bytecode::Label { labelled_break_block },
|
||||
{});
|
||||
}
|
||||
|
||||
generator.switch_to_basic_block(labelled_break_block);
|
||||
}
|
||||
|
||||
// 4. If stmtResult.[[Type]] is break and SameValue(stmtResult.[[Target]], label) is true, then
|
||||
// a. Set stmtResult to NormalCompletion(stmtResult.[[Value]]).
|
||||
// NOTE: These steps are performed by making labelled break jump straight to the appropriate break block, which preserves the statement result's value in the accumulator.
|
||||
|
||||
// 5. Return Completion(stmtResult).
|
||||
// NOTE: This is in the accumulator.
|
||||
return {};
|
||||
}
|
||||
|
||||
Bytecode::CodeGenerationErrorOr<void> IterationStatement::generate_labelled_evaluation(Bytecode::Generator&, Vector<FlyString> const&) const
|
||||
{
|
||||
return Bytecode::CodeGenerationError {
|
||||
this,
|
||||
"Missing generate_labelled_evaluation()"sv,
|
||||
};
|
||||
}
|
||||
|
||||
Bytecode::CodeGenerationErrorOr<void> WhileStatement::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{
|
||||
return generate_labelled_evaluation(generator, {});
|
||||
}
|
||||
|
||||
Bytecode::CodeGenerationErrorOr<void> WhileStatement::generate_labelled_evaluation(Bytecode::Generator& generator, Vector<FlyString> const& label_set) const
|
||||
{
|
||||
// test
|
||||
// jump if_false (true) end (false) body
|
||||
|
@ -646,8 +719,8 @@ Bytecode::CodeGenerationErrorOr<void> WhileStatement::generate_bytecode(Bytecode
|
|||
Bytecode::Label { end_block });
|
||||
|
||||
generator.switch_to_basic_block(body_block);
|
||||
generator.begin_continuable_scope(Bytecode::Label { test_block });
|
||||
generator.begin_breakable_scope(Bytecode::Label { end_block });
|
||||
generator.begin_continuable_scope(Bytecode::Label { test_block }, label_set);
|
||||
generator.begin_breakable_scope(Bytecode::Label { end_block }, label_set);
|
||||
TRY(m_body->generate_bytecode(generator));
|
||||
generator.end_breakable_scope();
|
||||
generator.end_continuable_scope();
|
||||
|
@ -664,6 +737,11 @@ Bytecode::CodeGenerationErrorOr<void> WhileStatement::generate_bytecode(Bytecode
|
|||
}
|
||||
|
||||
Bytecode::CodeGenerationErrorOr<void> DoWhileStatement::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{
|
||||
return generate_labelled_evaluation(generator, {});
|
||||
}
|
||||
|
||||
Bytecode::CodeGenerationErrorOr<void> DoWhileStatement::generate_labelled_evaluation(Bytecode::Generator& generator, Vector<FlyString> const& label_set) const
|
||||
{
|
||||
// jump always (true) body
|
||||
// test
|
||||
|
@ -692,8 +770,8 @@ Bytecode::CodeGenerationErrorOr<void> DoWhileStatement::generate_bytecode(Byteco
|
|||
Bytecode::Label { end_block });
|
||||
|
||||
generator.switch_to_basic_block(body_block);
|
||||
generator.begin_continuable_scope(Bytecode::Label { test_block });
|
||||
generator.begin_breakable_scope(Bytecode::Label { end_block });
|
||||
generator.begin_continuable_scope(Bytecode::Label { test_block }, label_set);
|
||||
generator.begin_breakable_scope(Bytecode::Label { end_block }, label_set);
|
||||
TRY(m_body->generate_bytecode(generator));
|
||||
generator.end_breakable_scope();
|
||||
generator.end_continuable_scope();
|
||||
|
@ -710,6 +788,11 @@ Bytecode::CodeGenerationErrorOr<void> DoWhileStatement::generate_bytecode(Byteco
|
|||
}
|
||||
|
||||
Bytecode::CodeGenerationErrorOr<void> ForStatement::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{
|
||||
return generate_labelled_evaluation(generator, {});
|
||||
}
|
||||
|
||||
Bytecode::CodeGenerationErrorOr<void> ForStatement::generate_labelled_evaluation(Bytecode::Generator& generator, Vector<FlyString> const& label_set) const
|
||||
{
|
||||
// init
|
||||
// jump always (true) test
|
||||
|
@ -732,6 +815,9 @@ Bytecode::CodeGenerationErrorOr<void> ForStatement::generate_bytecode(Bytecode::
|
|||
|
||||
bool has_lexical_environment = false;
|
||||
|
||||
// The breakable scope needs to start here to unwind the potentially created lexical environment for the init bytecode.
|
||||
generator.begin_breakable_scope(Bytecode::Label { end_block }, label_set);
|
||||
|
||||
if (m_init) {
|
||||
if (m_init->is_variable_declaration()) {
|
||||
auto& variable_declaration = verify_cast<VariableDeclaration>(*m_init);
|
||||
|
@ -783,10 +869,8 @@ Bytecode::CodeGenerationErrorOr<void> ForStatement::generate_bytecode(Bytecode::
|
|||
}
|
||||
|
||||
generator.switch_to_basic_block(*body_block_ptr);
|
||||
generator.begin_continuable_scope(Bytecode::Label { *update_block_ptr });
|
||||
generator.begin_breakable_scope(Bytecode::Label { end_block });
|
||||
generator.begin_continuable_scope(Bytecode::Label { *update_block_ptr }, label_set);
|
||||
TRY(m_body->generate_bytecode(generator));
|
||||
generator.end_breakable_scope();
|
||||
generator.end_continuable_scope();
|
||||
|
||||
if (!generator.is_current_block_terminated()) {
|
||||
|
@ -810,6 +894,7 @@ Bytecode::CodeGenerationErrorOr<void> ForStatement::generate_bytecode(Bytecode::
|
|||
if (has_lexical_environment)
|
||||
generator.end_variable_scope();
|
||||
|
||||
generator.end_breakable_scope();
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -1345,9 +1430,17 @@ Bytecode::CodeGenerationErrorOr<void> IfStatement::generate_bytecode(Bytecode::G
|
|||
|
||||
Bytecode::CodeGenerationErrorOr<void> ContinueStatement::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{
|
||||
generator.perform_needed_unwinds<Bytecode::Op::Jump>();
|
||||
if (m_target_label.is_null()) {
|
||||
generator.perform_needed_unwinds<Bytecode::Op::Jump>();
|
||||
generator.emit<Bytecode::Op::Jump>().set_targets(
|
||||
generator.nearest_continuable_scope(),
|
||||
{});
|
||||
return {};
|
||||
}
|
||||
|
||||
auto target_to_jump_to = generator.perform_needed_unwinds_for_labelled_continue_and_return_target_block(m_target_label);
|
||||
generator.emit<Bytecode::Op::Jump>().set_targets(
|
||||
generator.nearest_continuable_scope(),
|
||||
target_to_jump_to,
|
||||
{});
|
||||
return {};
|
||||
}
|
||||
|
@ -1524,9 +1617,17 @@ Bytecode::CodeGenerationErrorOr<void> ThrowStatement::generate_bytecode(Bytecode
|
|||
|
||||
Bytecode::CodeGenerationErrorOr<void> BreakStatement::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{
|
||||
generator.perform_needed_unwinds<Bytecode::Op::Jump>(true);
|
||||
if (m_target_label.is_null()) {
|
||||
generator.perform_needed_unwinds<Bytecode::Op::Jump>(true);
|
||||
generator.emit<Bytecode::Op::Jump>().set_targets(
|
||||
generator.nearest_breakable_scope(),
|
||||
{});
|
||||
return {};
|
||||
}
|
||||
|
||||
auto target_to_jump_to = generator.perform_needed_unwinds_for_labelled_break_and_return_target_block(m_target_label);
|
||||
generator.emit<Bytecode::Op::Jump>().set_targets(
|
||||
generator.nearest_breakable_scope(),
|
||||
target_to_jump_to,
|
||||
{});
|
||||
return {};
|
||||
}
|
||||
|
@ -1614,6 +1715,11 @@ Bytecode::CodeGenerationErrorOr<void> TryStatement::generate_bytecode(Bytecode::
|
|||
}
|
||||
|
||||
Bytecode::CodeGenerationErrorOr<void> SwitchStatement::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{
|
||||
return generate_labelled_evaluation(generator, {});
|
||||
}
|
||||
|
||||
Bytecode::CodeGenerationErrorOr<void> SwitchStatement::generate_labelled_evaluation(Bytecode::Generator& generator, Vector<FlyString> const& label_set) const
|
||||
{
|
||||
auto discriminant_reg = generator.allocate_register();
|
||||
TRY(m_discriminant->generate_bytecode(generator));
|
||||
|
@ -1651,7 +1757,7 @@ Bytecode::CodeGenerationErrorOr<void> SwitchStatement::generate_bytecode(Bytecod
|
|||
generator.emit<Bytecode::Op::Jump>().set_targets(Bytecode::Label { end_block }, {});
|
||||
}
|
||||
auto current_block = case_blocks.begin();
|
||||
generator.begin_breakable_scope(Bytecode::Label { end_block });
|
||||
generator.begin_breakable_scope(Bytecode::Label { end_block }, label_set);
|
||||
for (auto& switch_case : m_cases) {
|
||||
generator.switch_to_basic_block(*current_block);
|
||||
|
||||
|
@ -1858,7 +1964,7 @@ static Bytecode::CodeGenerationErrorOr<ForInOfHeadEvaluationResult> for_in_of_he
|
|||
}
|
||||
|
||||
// 14.7.5.7 ForIn/OfBodyEvaluation ( lhs, stmt, iteratorRecord, iterationKind, lhsKind, labelSet [ , iteratorKind ] ), https://tc39.es/ecma262/#sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset
|
||||
static Bytecode::CodeGenerationErrorOr<void> for_in_of_body_evaluation(Bytecode::Generator& generator, ASTNode const& node, Variant<NonnullRefPtr<ASTNode>, NonnullRefPtr<BindingPattern>> const& lhs, ASTNode const& body, ForInOfHeadEvaluationResult const& head_result, Bytecode::BasicBlock& loop_end, Bytecode::BasicBlock& loop_update)
|
||||
static Bytecode::CodeGenerationErrorOr<void> for_in_of_body_evaluation(Bytecode::Generator& generator, ASTNode const& node, Variant<NonnullRefPtr<ASTNode>, NonnullRefPtr<BindingPattern>> const& lhs, ASTNode const& body, ForInOfHeadEvaluationResult const& head_result, Vector<FlyString> const& label_set, Bytecode::BasicBlock& loop_end, Bytecode::BasicBlock& loop_update)
|
||||
{
|
||||
auto iterator_register = generator.allocate_register();
|
||||
generator.emit<Bytecode::Op::Store>(iterator_register);
|
||||
|
@ -1888,6 +1994,7 @@ static Bytecode::CodeGenerationErrorOr<void> for_in_of_body_evaluation(Bytecode:
|
|||
// 6. Repeat,
|
||||
generator.emit<Bytecode::Op::Jump>(Bytecode::Label { loop_update });
|
||||
generator.switch_to_basic_block(loop_update);
|
||||
generator.begin_continuable_scope(Bytecode::Label { loop_update }, label_set);
|
||||
|
||||
// a. Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]).
|
||||
generator.emit<Bytecode::Op::Load>(iterator_register);
|
||||
|
@ -2042,31 +2149,39 @@ static Bytecode::CodeGenerationErrorOr<void> for_in_of_body_evaluation(Bytecode:
|
|||
return {};
|
||||
}
|
||||
|
||||
// 14.7.5.5 Runtime Semantics: ForInOfLoopEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-forinofloopevaluation
|
||||
Bytecode::CodeGenerationErrorOr<void> ForInStatement::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{
|
||||
return generate_labelled_evaluation(generator, {});
|
||||
}
|
||||
|
||||
// 14.7.5.5 Runtime Semantics: ForInOfLoopEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-forinofloopevaluation
|
||||
Bytecode::CodeGenerationErrorOr<void> ForInStatement::generate_labelled_evaluation(Bytecode::Generator& generator, Vector<FlyString> const& label_set) const
|
||||
{
|
||||
auto& loop_end = generator.make_block();
|
||||
auto& loop_update = generator.make_block();
|
||||
generator.begin_breakable_scope(Bytecode::Label { loop_end });
|
||||
generator.begin_continuable_scope(Bytecode::Label { loop_update });
|
||||
generator.begin_breakable_scope(Bytecode::Label { loop_end }, label_set);
|
||||
|
||||
auto head_result = TRY(for_in_of_head_evaluation(generator, IterationKind::Enumerate, m_lhs, m_rhs));
|
||||
|
||||
// Now perform the rest of ForInOfLoopEvaluation, given that the accumulator holds the iterator we're supposed to iterate over.
|
||||
return for_in_of_body_evaluation(generator, *this, m_lhs, body(), head_result, loop_end, loop_update);
|
||||
return for_in_of_body_evaluation(generator, *this, m_lhs, body(), head_result, label_set, loop_end, loop_update);
|
||||
}
|
||||
|
||||
Bytecode::CodeGenerationErrorOr<void> ForOfStatement::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{
|
||||
return generate_labelled_evaluation(generator, {});
|
||||
}
|
||||
|
||||
Bytecode::CodeGenerationErrorOr<void> ForOfStatement::generate_labelled_evaluation(Bytecode::Generator& generator, Vector<FlyString> const& label_set) const
|
||||
{
|
||||
auto& loop_end = generator.make_block();
|
||||
auto& loop_update = generator.make_block();
|
||||
generator.begin_breakable_scope(Bytecode::Label { loop_end });
|
||||
generator.begin_continuable_scope(Bytecode::Label { loop_update });
|
||||
generator.begin_breakable_scope(Bytecode::Label { loop_end }, label_set);
|
||||
|
||||
auto head_result = TRY(for_in_of_head_evaluation(generator, IterationKind::Iterate, m_lhs, m_rhs));
|
||||
|
||||
// Now perform the rest of ForInOfLoopEvaluation, given that the accumulator holds the iterator we're supposed to iterate over.
|
||||
return for_in_of_body_evaluation(generator, *this, m_lhs, body(), head_result, loop_end, loop_update);
|
||||
return for_in_of_body_evaluation(generator, *this, m_lhs, body(), head_result, label_set, loop_end, loop_update);
|
||||
}
|
||||
|
||||
// 13.3.12.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-meta-properties-runtime-semantics-evaluation
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue