diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 1a811f8585..6806721221 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -377,13 +377,30 @@ Bytecode::CodeGenerationErrorOr AssignmentExpression::generate_bytecode(By // b. ReturnIfAbrupt(lref). Optional base_object_register; Optional computed_property_register; + Optional this_value_register; + + bool lhs_is_super_expression = false; if (is(*lhs)) { auto& expression = static_cast(*lhs); - TRY(expression.object().generate_bytecode(generator)); - + lhs_is_super_expression = is(expression.object()); base_object_register = generator.allocate_register(); - generator.emit(*base_object_register); + + if (!lhs_is_super_expression) { + TRY(expression.object().generate_bytecode(generator)); + generator.emit(*base_object_register); + } else { + // https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation + // 1. Let env be GetThisEnvironment(). + // 2. Let actualThis be ? env.GetThisBinding(). + this_value_register = generator.allocate_register(); + generator.emit(); + generator.emit(*this_value_register); + + // SuperProperty : super [ Expression ] + // 3. Let propertyNameReference be ? Evaluation of Expression. + // 4. Let propertyNameValue be ? GetValue(propertyNameReference). + } if (expression.is_computed()) { TRY(expression.property().generate_bytecode(generator)); @@ -401,6 +418,18 @@ Bytecode::CodeGenerationErrorOr AssignmentExpression::generate_bytecode(By "Unimplemented non-computed member expression"sv }; } + + if (lhs_is_super_expression) { + // 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(). + // 4. Return the Reference Record { [[Base]]: baseValue, [[ReferencedName]]: propertyKey, [[Strict]]: strict, [[ThisValue]]: actualThis }. + generator.emit(); + generator.emit(*base_object_register); + } } else if (is(*lhs)) { // NOTE: For Identifiers, we cannot perform GetVariable and then write into the reference it retrieves, only SetVariable can do this. // FIXME: However, this breaks spec as we are doing variable lookup after evaluating the RHS. This is observable in an object environment, where we visibly perform HasOwnProperty and Get(@@unscopables) on the binded object. @@ -428,10 +457,16 @@ Bytecode::CodeGenerationErrorOr AssignmentExpression::generate_bytecode(By auto& expression = static_cast(*lhs); if (expression.is_computed()) { - generator.emit(*base_object_register, *computed_property_register); + if (!lhs_is_super_expression) + generator.emit(*base_object_register, *computed_property_register); + else + generator.emit(*base_object_register, *computed_property_register, *this_value_register); } else if (expression.property().is_identifier()) { auto identifier_table_ref = generator.intern_identifier(verify_cast(expression.property()).string()); - generator.emit(*base_object_register, identifier_table_ref); + if (!lhs_is_super_expression) + generator.emit(*base_object_register, identifier_table_ref); + else + generator.emit(*base_object_register, *this_value_register, identifier_table_ref); } else if (expression.property().is_private_identifier()) { auto identifier_table_ref = generator.intern_identifier(verify_cast(expression.property()).string()); generator.emit(*base_object_register, identifier_table_ref); @@ -1435,11 +1470,11 @@ static Bytecode::CodeGenerationErrorOr get_base_and_value_from_member_expr auto super_base_register = generator.allocate_register(); generator.emit(super_base_register); generator.emit(*computed_property_value_register); - generator.emit(super_base_register); + generator.emit(super_base_register, this_reg); } 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); + generator.emit(identifier_table_ref, this_reg); } } else { TRY(member_expression.object().generate_bytecode(generator)); diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.cpp b/Userland/Libraries/LibJS/Bytecode/Generator.cpp index 27cefe8b50..f8cb53abbc 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Generator.cpp @@ -151,8 +151,9 @@ CodeGenerationErrorOr Generator::emit_load_from_reference(JS::ASTNode cons if (is(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) + auto this_register = allocate_register(); emit(); + emit(this_register); Optional computed_property_value_register; @@ -180,11 +181,11 @@ CodeGenerationErrorOr Generator::emit_load_from_reference(JS::ASTNode cons auto super_base_register = allocate_register(); emit(super_base_register); emit(*computed_property_value_register); - emit(super_base_register); + emit(super_base_register, this_register); } else { // 3. Let propertyKey be StringValue of IdentifierName. auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); - emit(identifier_table_ref); + emit(identifier_table_ref, this_register); } } else { TRY(expression.object().generate_bytecode(*this)); @@ -226,31 +227,76 @@ CodeGenerationErrorOr Generator::emit_store_to_reference(JS::ASTNode const emit(value_reg); auto& expression = static_cast(node); - TRY(expression.object().generate_bytecode(*this)); - auto object_reg = allocate_register(); - emit(object_reg); + // https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation + if (is(expression.object())) { + // 1. Let env be GetThisEnvironment(). + // 2. Let actualThis be ? env.GetThisBinding(). + auto this_register = allocate_register(); + emit(); + emit(this_register); + + Optional 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(*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(). + auto super_base_register = allocate_register(); + emit(); + emit(super_base_register); - if (expression.is_computed()) { - TRY(expression.property().generate_bytecode(*this)); - auto property_reg = allocate_register(); - emit(property_reg); emit(value_reg); - emit(object_reg, property_reg); - } else if (expression.property().is_identifier()) { - emit(value_reg); - auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); - emit(object_reg, identifier_table_ref); - } else if (expression.property().is_private_identifier()) { - emit(value_reg); - auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); - emit(object_reg, identifier_table_ref); + + // 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(super_base_register, *computed_property_value_register, this_register); + } else { + // 3. Let propertyKey be StringValue of IdentifierName. + auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); + emit(super_base_register, this_register, identifier_table_ref); + } } else { - return CodeGenerationError { - &expression, - "Unimplemented non-computed member expression"sv - }; + TRY(expression.object().generate_bytecode(*this)); + + auto object_reg = allocate_register(); + emit(object_reg); + + if (expression.is_computed()) { + TRY(expression.property().generate_bytecode(*this)); + auto property_reg = allocate_register(); + emit(property_reg); + emit(value_reg); + emit(object_reg, property_reg); + } else if (expression.property().is_identifier()) { + emit(value_reg); + auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); + emit(object_reg, identifier_table_ref); + } else if (expression.property().is_private_identifier()) { + emit(value_reg); + auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); + emit(object_reg, identifier_table_ref); + } else { + return CodeGenerationError { + &expression, + "Unimplemented non-computed member expression"sv + }; + } } + return {}; } diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h index 60ba1bba04..c79d7c4e84 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.h +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h @@ -34,7 +34,9 @@ O(EnterObjectEnvironment) \ O(Exp) \ O(GetById) \ + O(GetByIdWithThis) \ O(GetByValue) \ + O(GetByValueWithThis) \ O(GetIterator) \ O(GetMethod) \ O(GetNewTarget) \ @@ -80,7 +82,9 @@ O(Not) \ O(PushDeclarativeEnvironment) \ O(PutById) \ + O(PutByIdWithThis) \ O(PutByValue) \ + O(PutByValueWithThis) \ O(PutPrivateById) \ O(ResolveThisBinding) \ O(ResolveSuperBase) \ diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index d4ce875e0f..77069e7e6e 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -48,7 +48,7 @@ DeprecatedString Instruction::to_deprecated_string(Bytecode::Executable const& e namespace JS::Bytecode::Op { -static ThrowCompletionOr put_by_property_key(VM& vm, Value base, Value value, PropertyKey name, PropertyKind kind) +static ThrowCompletionOr put_by_property_key(VM& vm, Value base, Value this_value, Value value, PropertyKey name, PropertyKind kind) { auto object = TRY(base.to_object(vm)); if (kind == PropertyKind::Getter || kind == PropertyKind::Setter) { @@ -71,7 +71,7 @@ static ThrowCompletionOr put_by_property_key(VM& vm, Value base, Value val break; } case PropertyKind::KeyValue: { - bool succeeded = TRY(object->internal_set(name, value, base)); + bool succeeded = TRY(object->internal_set(name, value, this_value)); if (!succeeded && vm.in_strict_mode()) return vm.throw_completion(ErrorType::ReferenceNullishSetProperty, name, TRY_OR_THROW_OOM(vm, base.to_string_without_side_effects())); break; @@ -523,12 +523,11 @@ ThrowCompletionOr SetLocal::execute_impl(Bytecode::Interpreter& interprete return {}; } -ThrowCompletionOr GetById::execute_impl(Bytecode::Interpreter& interpreter) const +static ThrowCompletionOr get_by_id(Bytecode::Interpreter& interpreter, IdentifierTableIndex property, Value base_value, Value this_value) { auto& vm = interpreter.vm(); - auto const& name = interpreter.current_executable().get_identifier(m_property); - auto base_value = interpreter.accumulator(); + auto const& name = interpreter.current_executable().get_identifier(property); // OPTIMIZATION: For various primitives we can avoid actually creating a new object for them. GCPtr base_obj; @@ -547,10 +546,23 @@ ThrowCompletionOr GetById::execute_impl(Bytecode::Interpreter& interpreter base_obj = TRY(base_value.to_object(vm)); } - interpreter.accumulator() = TRY(base_obj->internal_get(name, base_value)); + interpreter.accumulator() = TRY(base_obj->internal_get(name, this_value)); return {}; } +ThrowCompletionOr GetById::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto base_value = interpreter.accumulator(); + return get_by_id(interpreter, m_property, base_value, base_value); +} + +ThrowCompletionOr GetByIdWithThis::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto base_value = interpreter.accumulator(); + auto this_value = interpreter.reg(m_this_value); + return get_by_id(interpreter, m_property, base_value, this_value); +} + ThrowCompletionOr GetPrivateById::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); @@ -582,7 +594,19 @@ ThrowCompletionOr PutById::execute_impl(Bytecode::Interpreter& interpreter auto value = interpreter.accumulator(); auto base = interpreter.reg(m_base); PropertyKey name = interpreter.current_executable().get_identifier(m_property); - TRY(put_by_property_key(vm, base, value, name, m_kind)); + TRY(put_by_property_key(vm, base, base, value, name, m_kind)); + interpreter.accumulator() = value; + return {}; +} + +ThrowCompletionOr PutByIdWithThis::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + // NOTE: Get the value from the accumulator before side effects have a chance to overwrite it. + auto value = interpreter.accumulator(); + auto base = interpreter.reg(m_base); + PropertyKey name = interpreter.current_executable().get_identifier(m_property); + TRY(put_by_property_key(vm, base, interpreter.reg(m_this_value), value, name, m_kind)); interpreter.accumulator() = value; return {}; } @@ -1042,6 +1066,21 @@ ThrowCompletionOr GetByValue::execute_impl(Bytecode::Interpreter& interpre return {}; } +ThrowCompletionOr GetByValueWithThis::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + + // NOTE: Get the property key from the accumulator before side effects have a chance to overwrite it. + auto property_key_value = interpreter.accumulator(); + + auto object = TRY(interpreter.reg(m_base).to_object(vm)); + + auto property_key = TRY(property_key_value.to_property_key(vm)); + + interpreter.accumulator() = TRY(object->internal_get(property_key, interpreter.reg(m_this_value))); + return {}; +} + ThrowCompletionOr PutByValue::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); @@ -1052,7 +1091,22 @@ ThrowCompletionOr PutByValue::execute_impl(Bytecode::Interpreter& interpre auto base = interpreter.reg(m_base); auto property_key = TRY(interpreter.reg(m_property).to_property_key(vm)); - TRY(put_by_property_key(vm, base, value, property_key, m_kind)); + TRY(put_by_property_key(vm, base, base, value, property_key, m_kind)); + interpreter.accumulator() = value; + return {}; +} + +ThrowCompletionOr PutByValueWithThis::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + + // NOTE: Get the value from the accumulator before side effects have a chance to overwrite it. + auto value = interpreter.accumulator(); + + auto base = interpreter.reg(m_base); + + auto property_key = TRY(interpreter.reg(m_property).to_property_key(vm)); + TRY(put_by_property_key(vm, base, interpreter.reg(m_this_value), value, property_key, m_kind)); interpreter.accumulator() = value; return {}; } @@ -1411,6 +1465,17 @@ DeprecatedString PutById::to_deprecated_string_impl(Bytecode::Executable const& return DeprecatedString::formatted("PutById kind:{} base:{}, property:{} ({})", kind, m_base, m_property, executable.identifier_table->get(m_property)); } +DeprecatedString PutByIdWithThis::to_deprecated_string_impl(Bytecode::Executable const& executable) const +{ + auto kind = m_kind == PropertyKind::Getter + ? "getter" + : m_kind == PropertyKind::Setter + ? "setter" + : "property"; + + return DeprecatedString::formatted("PutByIdWithThis kind:{} base:{}, property:{} ({}) this_value:{}", kind, m_base, m_property, executable.identifier_table->get(m_property), m_this_value); +} + DeprecatedString PutPrivateById::to_deprecated_string_impl(Bytecode::Executable const& executable) const { auto kind = m_kind == PropertyKind::Getter @@ -1427,6 +1492,11 @@ DeprecatedString GetById::to_deprecated_string_impl(Bytecode::Executable const& return DeprecatedString::formatted("GetById {} ({})", m_property, executable.identifier_table->get(m_property)); } +DeprecatedString GetByIdWithThis::to_deprecated_string_impl(Bytecode::Executable const& executable) const +{ + return DeprecatedString::formatted("GetByIdWithThis {} ({}) this_value:{}", m_property, executable.identifier_table->get(m_property), m_this_value); +} + DeprecatedString GetPrivateById::to_deprecated_string_impl(Bytecode::Executable const& executable) const { return DeprecatedString::formatted("GetPrivateById {} ({})", m_property, executable.identifier_table->get(m_property)); @@ -1611,6 +1681,11 @@ DeprecatedString GetByValue::to_deprecated_string_impl(Bytecode::Executable cons return DeprecatedString::formatted("GetByValue base:{}", m_base); } +DeprecatedString GetByValueWithThis::to_deprecated_string_impl(Bytecode::Executable const&) const +{ + return DeprecatedString::formatted("GetByValueWithThis base:{} this_value:{}", m_base, m_this_value); +} + DeprecatedString PutByValue::to_deprecated_string_impl(Bytecode::Executable const&) const { auto kind = m_kind == PropertyKind::Getter @@ -1622,6 +1697,17 @@ DeprecatedString PutByValue::to_deprecated_string_impl(Bytecode::Executable cons return DeprecatedString::formatted("PutByValue kind:{} base:{}, property:{}", kind, m_base, m_property); } +DeprecatedString PutByValueWithThis::to_deprecated_string_impl(Bytecode::Executable const&) const +{ + auto kind = m_kind == PropertyKind::Getter + ? "getter" + : m_kind == PropertyKind::Setter + ? "setter" + : "property"; + + return DeprecatedString::formatted("PutByValueWithThis kind:{} base:{}, property:{} this_value:{}", kind, m_base, m_property, m_this_value); +} + DeprecatedString DeleteByValue::to_deprecated_string_impl(Bytecode::Executable const&) const { return DeprecatedString::formatted("DeleteByValue base:{}", m_base); diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index 60fa75c8f3..1710f4d7b8 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -579,6 +579,29 @@ private: IdentifierTableIndex m_property; }; +class GetByIdWithThis final : public Instruction { +public: + GetByIdWithThis(IdentifierTableIndex property, Register this_value) + : Instruction(Type::GetByIdWithThis) + , m_property(property) + , m_this_value(this_value) + { + } + + ThrowCompletionOr 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 from, Register to) + { + if (m_this_value == from) + m_this_value = to; + } + +private: + IdentifierTableIndex m_property; + Register m_this_value; +}; + class GetPrivateById final : public Instruction { public: explicit GetPrivateById(IdentifierTableIndex property) @@ -646,6 +669,35 @@ private: PropertyKind m_kind; }; +class PutByIdWithThis final : public Instruction { +public: + PutByIdWithThis(Register base, Register this_value, IdentifierTableIndex property, PropertyKind kind = PropertyKind::KeyValue) + : Instruction(Type::PutByIdWithThis) + , m_base(base) + , m_this_value(this_value) + , m_property(property) + , m_kind(kind) + { + } + + ThrowCompletionOr 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 from, Register to) + { + if (m_base == from) + m_base = to; + if (m_this_value == from) + m_this_value = to; + } + +private: + Register m_base; + Register m_this_value; + IdentifierTableIndex m_property; + PropertyKind m_kind; +}; + class PutPrivateById final : public Instruction { public: explicit PutPrivateById(Register base, IdentifierTableIndex property, PropertyKind kind = PropertyKind::KeyValue) @@ -709,6 +761,31 @@ private: Register m_base; }; +class GetByValueWithThis final : public Instruction { +public: + GetByValueWithThis(Register base, Register this_value) + : Instruction(Type::GetByValueWithThis) + , m_base(base) + , m_this_value(this_value) + { + } + + ThrowCompletionOr 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 from, Register to) + { + if (m_base == from) + m_base = to; + if (m_this_value == from) + m_this_value = to; + } + +private: + Register m_base; + Register m_this_value; +}; + class PutByValue final : public Instruction { public: PutByValue(Register base, Register property, PropertyKind kind = PropertyKind::KeyValue) @@ -726,6 +803,8 @@ public: { if (m_base == from) m_base = to; + if (m_property == from) + m_property = to; } private: @@ -734,6 +813,37 @@ private: PropertyKind m_kind; }; +class PutByValueWithThis final : public Instruction { +public: + PutByValueWithThis(Register base, Register property, Register this_value, PropertyKind kind = PropertyKind::KeyValue) + : Instruction(Type::PutByValueWithThis) + , m_base(base) + , m_property(property) + , m_this_value(this_value) + , m_kind(kind) + { + } + + ThrowCompletionOr 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 from, Register to) + { + if (m_base == from) + m_base = to; + if (m_property == from) + m_property = to; + if (m_this_value == from) + m_this_value = to; + } + +private: + Register m_base; + Register m_property; + Register m_this_value; + PropertyKind m_kind; +}; + class DeleteByValue final : public Instruction { public: DeleteByValue(Register base) diff --git a/Userland/Libraries/LibJS/Bytecode/Pass/LoadElimination.cpp b/Userland/Libraries/LibJS/Bytecode/Pass/LoadElimination.cpp index 0619a13f01..f2bd173218 100644 --- a/Userland/Libraries/LibJS/Bytecode/Pass/LoadElimination.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Pass/LoadElimination.cpp @@ -122,9 +122,13 @@ static NonnullOwnPtr eliminate_loads(BasicBlock const& block, size_t // These can trigger proxies, which call into user code // So these are treated like calls case GetByValue: + case GetByValueWithThis: case GetById: + case GetByIdWithThis: case PutByValue: + case PutByValueWithThis: case PutById: + case PutByIdWithThis: // Attribute accesses (`a.o` or `a[o]`) may result in calls to getters or setters // or may trigger proxies // So these are treated like calls