diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index adfff4ced9..414a2c1460 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -1975,6 +1975,10 @@ public: virtual Completion execute(Interpreter&) const override; virtual ThrowCompletionOr to_reference(Interpreter&) const override; virtual void dump(int indent) const override; + virtual Bytecode::CodeGenerationErrorOr generate_bytecode(Bytecode::Generator&) const override; + + Expression const& base() const { return *m_base; } + Vector const& references() const { return m_references; } private: struct ReferenceAndValue { diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index ac999b69f7..9f0526e2f3 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -1279,6 +1279,66 @@ Bytecode::CodeGenerationErrorOr VariableDeclaration::generate_bytecode(Byt return {}; } +static Bytecode::CodeGenerationErrorOr get_base_and_value_from_member_expression(Bytecode::Generator& generator, MemberExpression const& member_expression, Bytecode::Register this_reg) +{ + // https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation + if (is(member_expression.object())) { + // 1. Let env be GetThisEnvironment(). + // 2. Let actualThis be ? env.GetThisBinding(). + generator.emit(); + generator.emit(this_reg); + + Optional computed_property_value_register; + + if (member_expression.is_computed()) { + // SuperProperty : super [ Expression ] + // 3. Let propertyNameReference be ? Evaluation of Expression. + // 4. Let propertyNameValue be ? GetValue(propertyNameReference). + TRY(member_expression.property().generate_bytecode(generator)); + computed_property_value_register = generator.allocate_register(); + generator.emit(*computed_property_value_register); + } + + // 5/7. Return ? MakeSuperPropertyReference(actualThis, propertyKey, strict). + + // https://tc39.es/ecma262/#sec-makesuperpropertyreference + // 1. Let env be GetThisEnvironment(). + // 2. Assert: env.HasSuperBinding() is true. + // 3. Let baseValue be ? env.GetSuperBase(). + generator.emit(); + + // 4. Return the Reference Record { [[Base]]: baseValue, [[ReferencedName]]: propertyKey, [[Strict]]: strict, [[ThisValue]]: actualThis }. + if (computed_property_value_register.has_value()) { + // 5. Let propertyKey be ? ToPropertyKey(propertyNameValue). + // FIXME: This does ToPropertyKey out of order, which is observable by Symbol.toPrimitive! + generator.emit(*computed_property_value_register); + } else { + // 3. Let propertyKey be StringValue of IdentifierName. + auto identifier_table_ref = generator.intern_identifier(verify_cast(member_expression.property()).string()); + generator.emit(identifier_table_ref); + } + } else { + TRY(member_expression.object().generate_bytecode(generator)); + generator.emit(this_reg); + if (member_expression.is_computed()) { + TRY(member_expression.property().generate_bytecode(generator)); + generator.emit(this_reg); + } else { + auto identifier_table_ref = [&] { + if (is(member_expression.property())) + return generator.intern_identifier(verify_cast(member_expression.property()).string()); + return generator.intern_identifier(verify_cast(member_expression.property()).string()); + }(); + + generator.emit(identifier_table_ref); + } + } + + return {}; +} + +static Bytecode::CodeGenerationErrorOr generate_optional_chain(Bytecode::Generator& generator, OptionalChain const& optional_chain, Bytecode::Register current_value_register, Bytecode::Register current_base_register); + Bytecode::CodeGenerationErrorOr CallExpression::generate_bytecode(Bytecode::Generator& generator) const { auto callee_reg = generator.allocate_register(); @@ -1291,61 +1351,11 @@ Bytecode::CodeGenerationErrorOr CallExpression::generate_bytecode(Bytecode generator.emit(callee_reg); } else if (is(*m_callee)) { auto& member_expression = static_cast(*m_callee); - - // https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation - if (is(member_expression.object())) { - // 1. Let env be GetThisEnvironment(). - // 2. Let actualThis be ? env.GetThisBinding(). - generator.emit(); - generator.emit(this_reg); - - Optional computed_property_value_register; - - if (member_expression.is_computed()) { - // SuperProperty : super [ Expression ] - // 3. Let propertyNameReference be ? Evaluation of Expression. - // 4. Let propertyNameValue be ? GetValue(propertyNameReference). - TRY(member_expression.property().generate_bytecode(generator)); - computed_property_value_register = generator.allocate_register(); - generator.emit(*computed_property_value_register); - } - - // 5/7. Return ? MakeSuperPropertyReference(actualThis, propertyKey, strict). - - // https://tc39.es/ecma262/#sec-makesuperpropertyreference - // 1. Let env be GetThisEnvironment(). - // 2. Assert: env.HasSuperBinding() is true. - // 3. Let baseValue be ? env.GetSuperBase(). - generator.emit(); - - // 4. Return the Reference Record { [[Base]]: baseValue, [[ReferencedName]]: propertyKey, [[Strict]]: strict, [[ThisValue]]: actualThis }. - if (computed_property_value_register.has_value()) { - // 5. Let propertyKey be ? ToPropertyKey(propertyNameValue). - // FIXME: This does ToPropertyKey out of order, which is observable by Symbol.toPrimitive! - generator.emit(*computed_property_value_register); - } else { - // 3. Let propertyKey be StringValue of IdentifierName. - auto identifier_table_ref = generator.intern_identifier(verify_cast(member_expression.property()).string()); - generator.emit(identifier_table_ref); - } - } else { - TRY(member_expression.object().generate_bytecode(generator)); - generator.emit(this_reg); - if (member_expression.is_computed()) { - TRY(member_expression.property().generate_bytecode(generator)); - generator.emit(this_reg); - } else { - auto identifier_table_ref = [&] { - if (is(member_expression.property())) - return generator.intern_identifier(verify_cast(member_expression.property()).string()); - return generator.intern_identifier(verify_cast(member_expression.property()).string()); - }(); - - generator.emit(identifier_table_ref); - } - } - + TRY(get_base_and_value_from_member_expression(generator, member_expression, this_reg)); generator.emit(callee_reg); + } else if (is(*m_callee)) { + auto& optional_chain = static_cast(*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 ClassFieldInitializerStatement::generate_b return {}; } +static Bytecode::CodeGenerationErrorOr generate_optional_chain(Bytecode::Generator& generator, OptionalChain const& optional_chain, Bytecode::Register current_value_register, Bytecode::Register current_base_register) +{ + if (is(optional_chain.base())) { + auto& member_expression = static_cast(optional_chain.base()); + TRY(get_base_and_value_from_member_expression(generator, member_expression, current_base_register)); + } else if (is(optional_chain.base())) { + auto& sub_optional_chain = static_cast(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(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::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 { + TRY(arguments_to_array_for_call(generator, call.arguments)); + generator.emit(Bytecode::Op::Call::CallType::Call, current_value_register, current_base_register); + + generator.emit(current_value_register); + + generator.emit(js_undefined()); + generator.emit(current_base_register); + + generator.emit(current_value_register); + return {}; + }, + [&](OptionalChain::ComputedReference const& ref) -> Bytecode::CodeGenerationErrorOr { + generator.emit(current_base_register); + TRY(ref.expression->generate_bytecode(generator)); + generator.emit(current_base_register); + generator.emit(current_value_register); + return {}; + }, + [&](OptionalChain::MemberReference const& ref) -> Bytecode::CodeGenerationErrorOr { + generator.emit(current_base_register); + generator.emit(generator.intern_identifier(ref.identifier->string())); + generator.emit(current_value_register); + return {}; + }, + [&](OptionalChain::PrivateMemberReference const&) -> Bytecode::CodeGenerationErrorOr { + return Bytecode::CodeGenerationError { + &optional_chain, + "Unimplemented reference: PrivateMemberReference"sv, + }; + })); + } + + generator.emit(Bytecode::Label { end_block }); + + generator.switch_to_basic_block(load_undefined_and_jump_to_end_block); + generator.emit(js_undefined()); + generator.emit(Bytecode::Label { end_block }); + + generator.switch_to_basic_block(end_block); + return {}; +} + +Bytecode::CodeGenerationErrorOr OptionalChain::generate_bytecode(Bytecode::Generator& generator) const +{ + auto current_base_register = generator.allocate_register(); + auto current_value_register = generator.allocate_register(); + generator.emit(js_undefined()); + generator.emit(current_base_register); + return generate_optional_chain(generator, *this, current_value_register, current_base_register); +} + }