mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-30 00:31:14 +00:00
LibJS/Bytecode: Implement the delete unary expression
`delete` has to operate directly on Reference Records, so this introduces a new set of operations called DeleteByValue, DeleteVariable and DeleteById. They operate similarly to their Get counterparts, except they end in creating a (temporary) Reference and calling delete_ on it.
This commit is contained in:
parent
589c3771e9
commit
7cc53b7ef1
Notes:
sideshowbarker
2024-07-17 16:38:10 +09:00
Author: https://github.com/Lubrsi Commit: https://github.com/SerenityOS/serenity/commit/7cc53b7ef1 Pull-request: https://github.com/SerenityOS/serenity/pull/13312 Reviewed-by: https://github.com/awesomekling Reviewed-by: https://github.com/linusg ✅
|
@ -427,6 +427,9 @@ Bytecode::CodeGenerationErrorOr<void> LogicalExpression::generate_bytecode(Bytec
|
|||
|
||||
Bytecode::CodeGenerationErrorOr<void> UnaryExpression::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{
|
||||
if (m_op == UnaryOp::Delete)
|
||||
return generator.emit_delete_reference(m_lhs);
|
||||
|
||||
TRY(m_lhs->generate_bytecode(generator));
|
||||
|
||||
switch (m_op) {
|
||||
|
@ -448,11 +451,9 @@ Bytecode::CodeGenerationErrorOr<void> UnaryExpression::generate_bytecode(Bytecod
|
|||
case UnaryOp::Void:
|
||||
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
|
||||
break;
|
||||
case UnaryOp::Delete: // Delete is implemented above.
|
||||
default:
|
||||
return Bytecode::CodeGenerationError {
|
||||
this,
|
||||
"Unimplemented operation"sv,
|
||||
};
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
return {};
|
||||
|
|
|
@ -200,6 +200,52 @@ CodeGenerationErrorOr<void> Generator::emit_store_to_reference(JS::ASTNode const
|
|||
};
|
||||
}
|
||||
|
||||
CodeGenerationErrorOr<void> Generator::emit_delete_reference(JS::ASTNode const& node)
|
||||
{
|
||||
if (is<Identifier>(node)) {
|
||||
auto& identifier = static_cast<Identifier const&>(node);
|
||||
emit<Bytecode::Op::DeleteVariable>(intern_identifier(identifier.string()));
|
||||
return {};
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
TRY(expression.property().generate_bytecode(*this));
|
||||
emit<Bytecode::Op::DeleteByValue>(object_reg);
|
||||
} else if (expression.property().is_identifier()) {
|
||||
auto identifier_table_ref = intern_identifier(verify_cast<Identifier>(expression.property()).string());
|
||||
emit<Bytecode::Op::DeleteById>(identifier_table_ref);
|
||||
} else {
|
||||
// NOTE: Trying to delete a private field generates a SyntaxError in the parser.
|
||||
return CodeGenerationError {
|
||||
&expression,
|
||||
"Unimplemented non-computed member expression"sv
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// Though this will have no deletion effect, we still have to evaluate the node as it can have side effects.
|
||||
// For example: delete a(); delete ++c.b; etc.
|
||||
|
||||
// 13.5.1.2 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation
|
||||
// 1. Let ref be the result of evaluating UnaryExpression.
|
||||
// 2. ReturnIfAbrupt(ref).
|
||||
TRY(node.generate_bytecode(*this));
|
||||
|
||||
// 3. If ref is not a Reference Record, return true.
|
||||
emit<Bytecode::Op::LoadImmediate>(Value(true));
|
||||
|
||||
// NOTE: The rest of the steps are handled by Delete{Variable,ByValue,Id}.
|
||||
return {};
|
||||
}
|
||||
|
||||
String CodeGenerationError::to_string()
|
||||
{
|
||||
return String::formatted("CodeGenerationError in {}: {}", failing_node ? failing_node->class_name() : "<unknown node>", reason_literal);
|
||||
|
|
|
@ -79,6 +79,7 @@ public:
|
|||
|
||||
CodeGenerationErrorOr<void> emit_load_from_reference(JS::ASTNode const&);
|
||||
CodeGenerationErrorOr<void> emit_store_to_reference(JS::ASTNode const&);
|
||||
CodeGenerationErrorOr<void> emit_delete_reference(JS::ASTNode const&);
|
||||
|
||||
void begin_continuable_scope(Label continue_target);
|
||||
void end_continuable_scope();
|
||||
|
|
|
@ -23,6 +23,9 @@
|
|||
O(CreateEnvironment) \
|
||||
O(CreateVariable) \
|
||||
O(Decrement) \
|
||||
O(DeleteById) \
|
||||
O(DeleteByValue) \
|
||||
O(DeleteVariable) \
|
||||
O(Div) \
|
||||
O(EnterUnwindContext) \
|
||||
O(EnterObjectEnvironment) \
|
||||
|
|
|
@ -263,6 +263,14 @@ ThrowCompletionOr<void> GetVariable::execute_impl(Bytecode::Interpreter& interpr
|
|||
return {};
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> DeleteVariable::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
auto const& string = interpreter.current_executable().get_identifier(m_identifier);
|
||||
auto reference = TRY(interpreter.vm().resolve_binding(string));
|
||||
interpreter.accumulator() = Value(TRY(reference.delete_(interpreter.global_object())));
|
||||
return {};
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> CreateEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
auto make_and_swap_envs = [&](auto*& old_environment) {
|
||||
|
@ -346,6 +354,16 @@ ThrowCompletionOr<void> PutById::execute_impl(Bytecode::Interpreter& interpreter
|
|||
return {};
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> DeleteById::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
auto* object = TRY(interpreter.accumulator().to_object(interpreter.global_object()));
|
||||
auto const& identifier = interpreter.current_executable().get_identifier(m_property);
|
||||
bool strict = interpreter.vm().in_strict_mode();
|
||||
auto reference = Reference { object, identifier, {}, strict };
|
||||
interpreter.accumulator() = Value(TRY(reference.delete_(interpreter.global_object())));
|
||||
return {};
|
||||
};
|
||||
|
||||
ThrowCompletionOr<void> Jump::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
interpreter.jump(*m_true_target);
|
||||
|
@ -578,6 +596,16 @@ ThrowCompletionOr<void> PutByValue::execute_impl(Bytecode::Interpreter& interpre
|
|||
return {};
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> DeleteByValue::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
auto* object = TRY(interpreter.reg(m_base).to_object(interpreter.global_object()));
|
||||
auto property_key = TRY(interpreter.accumulator().to_property_key(interpreter.global_object()));
|
||||
bool strict = interpreter.vm().in_strict_mode();
|
||||
auto reference = Reference { object, property_key, {}, strict };
|
||||
interpreter.accumulator() = Value(TRY(reference.delete_(interpreter.global_object())));
|
||||
return {};
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> GetIterator::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
{
|
||||
auto iterator = TRY(get_iterator(interpreter.global_object(), interpreter.accumulator()));
|
||||
|
@ -776,6 +804,11 @@ String GetVariable::to_string_impl(Bytecode::Executable const& executable) const
|
|||
return String::formatted("GetVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier));
|
||||
}
|
||||
|
||||
String DeleteVariable::to_string_impl(Bytecode::Executable const& executable) const
|
||||
{
|
||||
return String::formatted("DeleteVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier));
|
||||
}
|
||||
|
||||
String CreateEnvironment::to_string_impl(Bytecode::Executable const&) const
|
||||
{
|
||||
auto mode_string = m_mode == EnvironmentMode::Lexical
|
||||
|
@ -814,6 +847,11 @@ String GetById::to_string_impl(Bytecode::Executable const& executable) const
|
|||
return String::formatted("GetById {} ({})", m_property, executable.identifier_table->get(m_property));
|
||||
}
|
||||
|
||||
String DeleteById::to_string_impl(Bytecode::Executable const& executable) const
|
||||
{
|
||||
return String::formatted("DeleteById {} ({})", m_property, executable.identifier_table->get(m_property));
|
||||
}
|
||||
|
||||
String Jump::to_string_impl(Bytecode::Executable const&) const
|
||||
{
|
||||
if (m_true_target.has_value())
|
||||
|
@ -950,6 +988,11 @@ String PutByValue::to_string_impl(const Bytecode::Executable&) const
|
|||
return String::formatted("PutByValue base:{}, property:{}", m_base, m_property);
|
||||
}
|
||||
|
||||
String DeleteByValue::to_string_impl(Bytecode::Executable const&) const
|
||||
{
|
||||
return String::formatted("DeleteByValue base:{}", m_base);
|
||||
}
|
||||
|
||||
String GetIterator::to_string_impl(Executable const&) const
|
||||
{
|
||||
return "GetIterator";
|
||||
|
|
|
@ -378,6 +378,22 @@ private:
|
|||
Optional<EnvironmentCoordinate> mutable m_cached_environment_coordinate;
|
||||
};
|
||||
|
||||
class DeleteVariable final : public Instruction {
|
||||
public:
|
||||
explicit DeleteVariable(IdentifierTableIndex identifier)
|
||||
: Instruction(Type::DeleteVariable)
|
||||
, m_identifier(identifier)
|
||||
{
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
String to_string_impl(Bytecode::Executable const&) const;
|
||||
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
|
||||
|
||||
private:
|
||||
IdentifierTableIndex m_identifier;
|
||||
};
|
||||
|
||||
class GetById final : public Instruction {
|
||||
public:
|
||||
explicit GetById(IdentifierTableIndex property)
|
||||
|
@ -412,6 +428,22 @@ private:
|
|||
IdentifierTableIndex m_property;
|
||||
};
|
||||
|
||||
class DeleteById final : public Instruction {
|
||||
public:
|
||||
explicit DeleteById(IdentifierTableIndex property)
|
||||
: Instruction(Type::DeleteById)
|
||||
, m_property(property)
|
||||
{
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
String to_string_impl(Bytecode::Executable const&) const;
|
||||
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
|
||||
|
||||
private:
|
||||
IdentifierTableIndex m_property;
|
||||
};
|
||||
|
||||
class GetByValue final : public Instruction {
|
||||
public:
|
||||
explicit GetByValue(Register base)
|
||||
|
@ -446,6 +478,22 @@ private:
|
|||
Register m_property;
|
||||
};
|
||||
|
||||
class DeleteByValue final : public Instruction {
|
||||
public:
|
||||
DeleteByValue(Register base)
|
||||
: Instruction(Type::DeleteByValue)
|
||||
, m_base(base)
|
||||
{
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
String to_string_impl(Bytecode::Executable const&) const;
|
||||
void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
|
||||
|
||||
private:
|
||||
Register m_base;
|
||||
};
|
||||
|
||||
class Jump : public Instruction {
|
||||
public:
|
||||
constexpr static bool IsTerminator = true;
|
||||
|
|
Loading…
Reference in a new issue