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:
Luke Wilde 2022-03-27 19:50:09 +01:00 committed by Andreas Kling
parent 589c3771e9
commit 7cc53b7ef1
Notes: sideshowbarker 2024-07-17 16:38:10 +09:00
6 changed files with 146 additions and 4 deletions

View file

@ -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 {};

View file

@ -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);

View file

@ -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();

View file

@ -23,6 +23,9 @@
O(CreateEnvironment) \
O(CreateVariable) \
O(Decrement) \
O(DeleteById) \
O(DeleteByValue) \
O(DeleteVariable) \
O(Div) \
O(EnterUnwindContext) \
O(EnterObjectEnvironment) \

View file

@ -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";

View file

@ -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;