1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-06-01 12:58:13 +00:00

LibJS/Bytecode: Implement optional chaining

This commit is contained in:
Luke Wilde 2023-06-17 14:50:23 +01:00 committed by Andreas Kling
parent c9088fa6ec
commit 1116ba191a
2 changed files with 148 additions and 54 deletions

View file

@ -1975,6 +1975,10 @@ public:
virtual Completion execute(Interpreter&) const override;
virtual ThrowCompletionOr<JS::Reference> to_reference(Interpreter&) const override;
virtual void dump(int indent) const override;
virtual Bytecode::CodeGenerationErrorOr<void> generate_bytecode(Bytecode::Generator&) const override;
Expression const& base() const { return *m_base; }
Vector<Reference> const& references() const { return m_references; }
private:
struct ReferenceAndValue {

View file

@ -1279,19 +1279,8 @@ Bytecode::CodeGenerationErrorOr<void> VariableDeclaration::generate_bytecode(Byt
return {};
}
Bytecode::CodeGenerationErrorOr<void> CallExpression::generate_bytecode(Bytecode::Generator& generator) const
static Bytecode::CodeGenerationErrorOr<void> get_base_and_value_from_member_expression(Bytecode::Generator& generator, MemberExpression const& member_expression, Bytecode::Register this_reg)
{
auto callee_reg = generator.allocate_register();
auto this_reg = generator.allocate_register();
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
generator.emit<Bytecode::Op::Store>(this_reg);
if (is<NewExpression>(this)) {
TRY(m_callee->generate_bytecode(generator));
generator.emit<Bytecode::Op::Store>(callee_reg);
} else if (is<MemberExpression>(*m_callee)) {
auto& member_expression = static_cast<MemberExpression const&>(*m_callee);
// https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation
if (is<SuperExpression>(member_expression.object())) {
// 1. Let env be GetThisEnvironment().
@ -1345,7 +1334,28 @@ Bytecode::CodeGenerationErrorOr<void> CallExpression::generate_bytecode(Bytecode
}
}
return {};
}
static Bytecode::CodeGenerationErrorOr<void> generate_optional_chain(Bytecode::Generator& generator, OptionalChain const& optional_chain, Bytecode::Register current_value_register, Bytecode::Register current_base_register);
Bytecode::CodeGenerationErrorOr<void> CallExpression::generate_bytecode(Bytecode::Generator& generator) const
{
auto callee_reg = generator.allocate_register();
auto this_reg = generator.allocate_register();
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
generator.emit<Bytecode::Op::Store>(this_reg);
if (is<NewExpression>(this)) {
TRY(m_callee->generate_bytecode(generator));
generator.emit<Bytecode::Op::Store>(callee_reg);
} else if (is<MemberExpression>(*m_callee)) {
auto& member_expression = static_cast<MemberExpression const&>(*m_callee);
TRY(get_base_and_value_from_member_expression(generator, member_expression, this_reg));
generator.emit<Bytecode::Op::Store>(callee_reg);
} else if (is<OptionalChain>(*m_callee)) {
auto& optional_chain = static_cast<OptionalChain const&>(*m_callee);
TRY(generate_optional_chain(generator, optional_chain, callee_reg, this_reg));
} else {
// FIXME: this = global object in sloppy mode.
TRY(m_callee->generate_bytecode(generator));
@ -2604,4 +2614,84 @@ Bytecode::CodeGenerationErrorOr<void> ClassFieldInitializerStatement::generate_b
return {};
}
static Bytecode::CodeGenerationErrorOr<void> generate_optional_chain(Bytecode::Generator& generator, OptionalChain const& optional_chain, Bytecode::Register current_value_register, Bytecode::Register current_base_register)
{
if (is<MemberExpression>(optional_chain.base())) {
auto& member_expression = static_cast<MemberExpression const&>(optional_chain.base());
TRY(get_base_and_value_from_member_expression(generator, member_expression, current_base_register));
} else if (is<OptionalChain>(optional_chain.base())) {
auto& sub_optional_chain = static_cast<OptionalChain const&>(optional_chain.base());
TRY(generate_optional_chain(generator, sub_optional_chain, current_value_register, current_base_register));
} else {
TRY(optional_chain.base().generate_bytecode(generator));
}
generator.emit<Bytecode::Op::Store>(current_value_register);
auto& load_undefined_and_jump_to_end_block = generator.make_block();
auto& end_block = generator.make_block();
for (auto& reference : optional_chain.references()) {
auto is_optional = reference.visit([](auto& ref) { return ref.mode; }) == OptionalChain::Mode::Optional;
if (is_optional) {
auto& not_nullish_block = generator.make_block();
generator.emit<Bytecode::Op::JumpNullish>(
Bytecode::Label { load_undefined_and_jump_to_end_block },
Bytecode::Label { not_nullish_block });
generator.switch_to_basic_block(not_nullish_block);
}
TRY(reference.visit(
[&](OptionalChain::Call const& call) -> Bytecode::CodeGenerationErrorOr<void> {
TRY(arguments_to_array_for_call(generator, call.arguments));
generator.emit<Bytecode::Op::Call>(Bytecode::Op::Call::CallType::Call, current_value_register, current_base_register);
generator.emit<Bytecode::Op::Store>(current_value_register);
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
generator.emit<Bytecode::Op::Store>(current_base_register);
generator.emit<Bytecode::Op::Load>(current_value_register);
return {};
},
[&](OptionalChain::ComputedReference const& ref) -> Bytecode::CodeGenerationErrorOr<void> {
generator.emit<Bytecode::Op::Store>(current_base_register);
TRY(ref.expression->generate_bytecode(generator));
generator.emit<Bytecode::Op::GetByValue>(current_base_register);
generator.emit<Bytecode::Op::Store>(current_value_register);
return {};
},
[&](OptionalChain::MemberReference const& ref) -> Bytecode::CodeGenerationErrorOr<void> {
generator.emit<Bytecode::Op::Store>(current_base_register);
generator.emit<Bytecode::Op::GetById>(generator.intern_identifier(ref.identifier->string()));
generator.emit<Bytecode::Op::Store>(current_value_register);
return {};
},
[&](OptionalChain::PrivateMemberReference const&) -> Bytecode::CodeGenerationErrorOr<void> {
return Bytecode::CodeGenerationError {
&optional_chain,
"Unimplemented reference: PrivateMemberReference"sv,
};
}));
}
generator.emit<Bytecode::Op::Jump>(Bytecode::Label { end_block });
generator.switch_to_basic_block(load_undefined_and_jump_to_end_block);
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
generator.emit<Bytecode::Op::Jump>(Bytecode::Label { end_block });
generator.switch_to_basic_block(end_block);
return {};
}
Bytecode::CodeGenerationErrorOr<void> OptionalChain::generate_bytecode(Bytecode::Generator& generator) const
{
auto current_base_register = generator.allocate_register();
auto current_value_register = generator.allocate_register();
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
generator.emit<Bytecode::Op::Store>(current_base_register);
return generate_optional_chain(generator, *this, current_value_register, current_base_register);
}
}