Ver Fonte

LibJS/Bytecode: Implement initial support for super member expressions

Luke Wilde há 2 anos atrás
pai
commit
b15128c45b

+ 45 - 17
Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

@@ -1523,29 +1523,57 @@ Bytecode::CodeGenerationErrorOr<void> CallExpression::generate_bytecode(Bytecode
     if (is<NewExpression>(this)) {
         TRY(m_callee->generate_bytecode(generator));
         generator.emit<Bytecode::Op::Store>(callee_reg);
-    } else if (is<SuperExpression>(*m_callee)) {
-        return Bytecode::CodeGenerationError {
-            this,
-            "Unimplemented callee kind: SuperExpression"sv,
-        };
     } 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())) {
-            return Bytecode::CodeGenerationError {
-                this,
-                "Unimplemented callee kind: MemberExpression on SuperExpression"sv,
-            };
-        }
+            // 1. Let env be GetThisEnvironment().
+            // 2. Let actualThis be ? env.GetThisBinding().
+            generator.emit<Bytecode::Op::ResolveThisBinding>();
+            generator.emit<Bytecode::Op::Store>(this_reg);
+
+            Optional<Bytecode::Register> 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<Bytecode::Op::Store>(*computed_property_value_register);
+            }
 
-        TRY(member_expression.object().generate_bytecode(generator));
-        generator.emit<Bytecode::Op::Store>(this_reg);
-        if (member_expression.is_computed()) {
-            TRY(member_expression.property().generate_bytecode(generator));
-            generator.emit<Bytecode::Op::GetByValue>(this_reg);
+            // 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<Bytecode::Op::ResolveSuperBase>();
+
+            // 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<Bytecode::Op::GetByValue>(*computed_property_value_register);
+            } else {
+                // 3. Let propertyKey be StringValue of IdentifierName.
+                auto identifier_table_ref = generator.intern_identifier(verify_cast<Identifier>(member_expression.property()).string());
+                generator.emit<Bytecode::Op::GetById>(identifier_table_ref);
+            }
         } else {
-            auto identifier_table_ref = generator.intern_identifier(verify_cast<Identifier>(member_expression.property()).string());
-            generator.emit<Bytecode::Op::GetById>(identifier_table_ref);
+            TRY(member_expression.object().generate_bytecode(generator));
+            generator.emit<Bytecode::Op::Store>(this_reg);
+            if (member_expression.is_computed()) {
+                TRY(member_expression.property().generate_bytecode(generator));
+                generator.emit<Bytecode::Op::GetByValue>(this_reg);
+            } else {
+                auto identifier_table_ref = generator.intern_identifier(verify_cast<Identifier>(member_expression.property()).string());
+                generator.emit<Bytecode::Op::GetById>(identifier_table_ref);
+            }
         }
+
         generator.emit<Bytecode::Op::Store>(callee_reg);
     } else {
         // FIXME: this = global object in sloppy mode.

+ 52 - 13
Userland/Libraries/LibJS/Bytecode/Generator.cpp

@@ -151,22 +151,61 @@ CodeGenerationErrorOr<void> Generator::emit_load_from_reference(JS::ASTNode cons
     }
     if (is<MemberExpression>(node)) {
         auto& expression = static_cast<MemberExpression const&>(node);
-        TRY(expression.object().generate_bytecode(*this));
 
-        if (expression.is_computed()) {
-            auto object_reg = allocate_register();
-            emit<Bytecode::Op::Store>(object_reg);
+        // https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation
+        if (is<SuperExpression>(expression.object())) {
+            // 1. Let env be GetThisEnvironment().
+            // 2. Let actualThis be ? env.GetThisBinding().
+            // NOTE: Whilst this isn't used, it's still observable (e.g. it throws if super() hasn't been called)
+            emit<Bytecode::Op::ResolveThisBinding>();
+
+            Optional<Bytecode::Register> computed_property_value_register;
+
+            if (expression.is_computed()) {
+                // SuperProperty : super [ Expression ]
+                // 3. Let propertyNameReference be ? Evaluation of Expression.
+                // 4. Let propertyNameValue be ? GetValue(propertyNameReference).
+                TRY(expression.property().generate_bytecode(*this));
+                computed_property_value_register = allocate_register();
+                emit<Bytecode::Op::Store>(*computed_property_value_register);
+            }
 
-            TRY(expression.property().generate_bytecode(*this));
-            emit<Bytecode::Op::GetByValue>(object_reg);
-        } else if (expression.property().is_identifier()) {
-            auto identifier_table_ref = intern_identifier(verify_cast<Identifier>(expression.property()).string());
-            emit<Bytecode::Op::GetById>(identifier_table_ref);
+            // 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().
+            emit<Bytecode::Op::ResolveSuperBase>();
+
+            // 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!
+                emit<Bytecode::Op::GetByValue>(*computed_property_value_register);
+            } else {
+                // 3. Let propertyKey be StringValue of IdentifierName.
+                auto identifier_table_ref = intern_identifier(verify_cast<Identifier>(expression.property()).string());
+                emit<Bytecode::Op::GetById>(identifier_table_ref);
+            }
         } else {
-            return CodeGenerationError {
-                &expression,
-                "Unimplemented non-computed member expression"sv
-            };
+            TRY(expression.object().generate_bytecode(*this));
+
+            if (expression.is_computed()) {
+                auto object_reg = allocate_register();
+                emit<Bytecode::Op::Store>(object_reg);
+
+                TRY(expression.property().generate_bytecode(*this));
+                emit<Bytecode::Op::GetByValue>(object_reg);
+            } else if (expression.property().is_identifier()) {
+                auto identifier_table_ref = intern_identifier(verify_cast<Identifier>(expression.property()).string());
+                emit<Bytecode::Op::GetById>(identifier_table_ref);
+            } else {
+                return CodeGenerationError {
+                    &expression,
+                    "Unimplemented non-computed member expression"sv
+                };
+            }
         }
         return {};
     }

+ 1 - 0
Userland/Libraries/LibJS/Bytecode/Instruction.h

@@ -76,6 +76,7 @@
     O(PutById)                       \
     O(PutByValue)                    \
     O(ResolveThisBinding)            \
+    O(ResolveSuperBase)              \
     O(Return)                        \
     O(RightShift)                    \
     O(ScheduleJump)                  \

+ 25 - 0
Userland/Libraries/LibJS/Bytecode/Op.cpp

@@ -530,6 +530,26 @@ ThrowCompletionOr<void> ResolveThisBinding::execute_impl(Bytecode::Interpreter&
     return {};
 }
 
+// https://tc39.es/ecma262/#sec-makesuperpropertyreference
+ThrowCompletionOr<void> ResolveSuperBase::execute_impl(Bytecode::Interpreter& interpreter) const
+{
+    auto& vm = interpreter.vm();
+
+    // 1. Let env be GetThisEnvironment().
+    auto& env = verify_cast<FunctionEnvironment>(*get_this_environment(vm));
+
+    // 2. Assert: env.HasSuperBinding() is true.
+    VERIFY(env.has_super_binding());
+
+    // 3. Let baseValue be ? env.GetSuperBase().
+    auto base_value = TRY(env.get_super_base());
+
+    // 4. Let bv be ? RequireObjectCoercible(baseValue).
+    interpreter.accumulator() = TRY(require_object_coercible(vm, base_value));
+
+    return {};
+}
+
 ThrowCompletionOr<void> GetNewTarget::execute_impl(Bytecode::Interpreter& interpreter) const
 {
     interpreter.accumulator() = interpreter.vm().get_new_target();
@@ -1383,6 +1403,11 @@ DeprecatedString ResolveThisBinding::to_deprecated_string_impl(Bytecode::Executa
     return "ResolveThisBinding"sv;
 }
 
+DeprecatedString ResolveSuperBase::to_deprecated_string_impl(Bytecode::Executable const&) const
+{
+    return "ResolveSuperBase"sv;
+}
+
 DeprecatedString GetNewTarget::to_deprecated_string_impl(Bytecode::Executable const&) const
 {
     return "GetNewTarget"sv;

+ 13 - 0
Userland/Libraries/LibJS/Bytecode/Op.h

@@ -1130,6 +1130,19 @@ public:
     void replace_references_impl(Register, Register) { }
 };
 
+class ResolveSuperBase final : public Instruction {
+public:
+    explicit ResolveSuperBase()
+        : Instruction(Type::ResolveSuperBase)
+    {
+    }
+
+    ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
+    DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
+    void replace_references_impl(Register, Register) { }
+};
+
 class GetNewTarget final : public Instruction {
 public:
     explicit GetNewTarget()