From 21ae882cfd9a6fb1a95c092916bea346445ea4aa Mon Sep 17 00:00:00 2001 From: Hendiadyoin1 Date: Tue, 30 Aug 2022 18:03:02 +0200 Subject: [PATCH] LibJS: Implement SuperCall for the Bytecode-VM --- Userland/Libraries/LibJS/AST.h | 1 + .../Libraries/LibJS/Bytecode/ASTCodegen.cpp | 28 ++++++++ .../Libraries/LibJS/Bytecode/Instruction.h | 1 + Userland/Libraries/LibJS/Bytecode/Op.cpp | 69 +++++++++++++++++++ Userland/Libraries/LibJS/Bytecode/Op.h | 33 ++++++++- 5 files changed, 130 insertions(+), 2 deletions(-) diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index 87f7e9ae5f..13cf6f55d9 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -1516,6 +1516,7 @@ public: virtual Completion execute(Interpreter&) const override; virtual void dump(int indent) const override; + virtual Bytecode::CodeGenerationErrorOr generate_bytecode(Bytecode::Generator&) const override; private: Vector const m_arguments; diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 5a4e8dd37f..7c3bec1fe7 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -521,6 +521,34 @@ Bytecode::CodeGenerationErrorOr Identifier::generate_bytecode(Bytecode::Ge return {}; } +Bytecode::CodeGenerationErrorOr SuperCall::generate_bytecode(Bytecode::Generator& generator) const +{ + Vector argument_registers; + + if (m_is_synthetic == IsPartOfSyntheticConstructor::Yes) { + // NOTE: This is the case where we have a fake constructor(...args) { super(...args); } which + // shouldn't call @@iterator of %Array.prototype%. + VERIFY(m_arguments.size() == 1); + VERIFY(m_arguments[0].is_spread); + auto const& argument = m_arguments[0]; + // This generates a single argument, which will be implicitly passed in accumulator + MUST(argument.value->generate_bytecode(generator)); + } else { + argument_registers.ensure_capacity(m_arguments.size()); + + for (auto const& arg : m_arguments) { + TRY(arg.value->generate_bytecode(generator)); + auto arg_reg = generator.allocate_register(); + generator.emit(arg_reg); + argument_registers.unchecked_append(arg_reg); + } + } + + generator.emit_with_extra_register_slots(argument_registers.size(), m_is_synthetic == IsPartOfSyntheticConstructor::Yes, argument_registers); + + return {}; +} + static Bytecode::CodeGenerationErrorOr generate_binding_pattern_bytecode(Bytecode::Generator& generator, BindingPattern const& pattern, Bytecode::Op::SetVariable::InitializationMode, Bytecode::Register const& value_reg); Bytecode::CodeGenerationErrorOr AssignmentExpression::generate_bytecode(Bytecode::Generator& generator) const diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h index d5accd5586..befd238fc1 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.h +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h @@ -80,6 +80,7 @@ O(StrictlyEquals) \ O(StrictlyInequals) \ O(Sub) \ + O(SuperCall) \ O(Throw) \ O(Typeof) \ O(TypeofVariable) \ diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index b931410d70..a9f41f994b 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -525,6 +526,60 @@ ThrowCompletionOr Call::execute_impl(Bytecode::Interpreter& interpreter) c return {}; } +// 13.3.7.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation +ThrowCompletionOr SuperCall::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + // 1. Let newTarget be GetNewTarget(). + auto new_target = vm.get_new_target(); + + // 2. Assert: Type(newTarget) is Object. + VERIFY(new_target.is_object()); + + // 3. Let func be GetSuperConstructor(). + auto* func = get_super_constructor(vm); + + // 4. Let argList be ? ArgumentListEvaluation of Arguments. + MarkedVector arg_list { vm.heap() }; + if (m_is_synthetic) { + auto const& value = interpreter.accumulator(); + VERIFY(value.is_object() && is(value.as_object())); + auto const& array_value = static_cast(value.as_object()); + auto length = MUST(length_of_array_like(vm, array_value)); + for (size_t i = 0; i < length; ++i) + arg_list.append(array_value.get_without_side_effects(PropertyKey { i })); + } else { + for (size_t i = 0; i < m_argument_count; ++i) + arg_list.append(interpreter.reg(m_arguments[i])); + } + + // 5. If IsConstructor(func) is false, throw a TypeError exception. + if (!Value(func).is_constructor()) + return vm.throw_completion(ErrorType::NotAConstructor, "Super constructor"); + + // 6. Let result be ? Construct(func, argList, newTarget). + auto* result = TRY(construct(vm, static_cast(*func), move(arg_list), &new_target.as_function())); + + // 7. Let thisER be GetThisEnvironment(). + auto& this_environment = verify_cast(get_this_environment(vm)); + + // 8. Perform ? thisER.BindThisValue(result). + TRY(this_environment.bind_this_value(vm, result)); + + // 9. Let F be thisER.[[FunctionObject]]. + auto& f = this_environment.function_object(); + + // 10. Assert: F is an ECMAScript function object. + // NOTE: This is implied by the strong C++ type. + + // 11. Perform ? InitializeInstanceElements(result, F). + TRY(vm.initialize_instance_elements(*result, f)); + + // 12. Return result. + interpreter.accumulator() = result; + return {}; +} + ThrowCompletionOr NewFunction::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); @@ -1000,6 +1055,20 @@ String Call::to_string_impl(Bytecode::Executable const&) const return builder.to_string(); } +String SuperCall::to_string_impl(Bytecode::Executable const&) const +{ + StringBuilder builder; + builder.append("SuperCall"sv); + if (m_is_synthetic) { + builder.append(" arguments:[...acc]"sv); + } else if (m_argument_count != 0) { + builder.append(" arguments:["sv); + builder.join(", "sv, Span(m_arguments, m_argument_count)); + builder.append(']'); + } + return builder.to_string(); +} + String NewFunction::to_string_impl(Bytecode::Executable const&) const { return "NewFunction"; diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index 2217136147..3be84e0465 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -613,6 +613,33 @@ private: Register m_arguments[]; }; +// NOTE: This instruction is variable-width depending on the number of arguments! +class SuperCall : public Instruction { +public: + explicit SuperCall(bool is_synthetic, Vector const& arguments) + : Instruction(Type::SuperCall) + , m_is_synthetic(is_synthetic) + , m_argument_count(arguments.size()) + { + for (size_t i = 0; i < m_argument_count; ++i) + m_arguments[i] = arguments[i]; + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + String to_string_impl(Bytecode::Executable const&) const; + void replace_references_impl(BasicBlock const&, BasicBlock const&) { } + + size_t length_impl() const + { + return sizeof(*this) + sizeof(Register) * m_argument_count; + } + +private: + bool m_is_synthetic; + size_t m_argument_count { 0 }; + Register m_arguments[]; +}; + class NewClass final : public Instruction { public: explicit NewClass(ClassExpression const& class_expression) @@ -966,9 +993,11 @@ ALWAYS_INLINE size_t Instruction::length() const { if (type() == Type::Call) return static_cast(*this).length_impl(); - else if (type() == Type::NewArray) + if (type() == Type::SuperCall) + return static_cast(*this).length_impl(); + if (type() == Type::NewArray) return static_cast(*this).length_impl(); - else if (type() == Type::CopyObjectExcludingProperties) + if (type() == Type::CopyObjectExcludingProperties) return static_cast(*this).length_impl(); #define __BYTECODE_OP(op) \