Skip to content

Commit

Permalink
LibJS/Bytecode: Add codegen for "named evaluation if anonymous function"
Browse files Browse the repository at this point in the history
This gives anonymous functions the name from the LHS they are being
assigned to.

171 new passes on test262. :^)
  • Loading branch information
awesomekling committed Jun 23, 2023
1 parent a92d94f commit 6453d91
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 27 deletions.
7 changes: 3 additions & 4 deletions Userland/Libraries/LibJS/AST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -302,21 +302,20 @@ Completion FunctionExpression::execute(Interpreter& interpreter) const
InterpreterNodeScope node_scope { interpreter, *this };

// 1. Return InstantiateOrdinaryFunctionExpression of FunctionExpression.
return instantiate_ordinary_function_expression(interpreter, name());
return instantiate_ordinary_function_expression(interpreter.vm(), name());
}

// 15.2.5 Runtime Semantics: InstantiateOrdinaryFunctionExpression, https://tc39.es/ecma262/#sec-runtime-semantics-instantiateordinaryfunctionexpression
Value FunctionExpression::instantiate_ordinary_function_expression(Interpreter& interpreter, DeprecatedFlyString given_name) const
Value FunctionExpression::instantiate_ordinary_function_expression(VM& vm, DeprecatedFlyString given_name) const
{
auto& vm = interpreter.vm();
auto& realm = *vm.current_realm();

if (given_name.is_empty())
given_name = "";
auto has_own_name = !name().is_empty();

auto const& used_name = has_own_name ? name() : given_name;
auto environment = NonnullGCPtr { *interpreter.lexical_environment() };
auto environment = NonnullGCPtr { *vm.running_execution_context().lexical_environment };
if (has_own_name) {
VERIFY(environment);
environment = new_declarative_environment(*environment);
Expand Down
4 changes: 3 additions & 1 deletion Userland/Libraries/LibJS/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -734,10 +734,11 @@ class FunctionExpression final
virtual void dump(int indent) const override;

virtual Bytecode::CodeGenerationErrorOr<void> generate_bytecode(Bytecode::Generator&) const override;
virtual Bytecode::CodeGenerationErrorOr<void> generate_bytecode_with_lhs_name(Bytecode::Generator&, Optional<DeprecatedFlyString const&> lhs_name) const;

bool has_name() const { return !name().is_empty(); }

Value instantiate_ordinary_function_expression(Interpreter&, DeprecatedFlyString given_name) const;
Value instantiate_ordinary_function_expression(VM&, DeprecatedFlyString given_name) const;

private:
virtual bool is_function_expression() const override { return true; }
Expand Down Expand Up @@ -1414,6 +1415,7 @@ class ClassExpression final : public Expression {
virtual Completion execute(Interpreter&) const override;
virtual void dump(int indent) const override;
virtual Bytecode::CodeGenerationErrorOr<void> generate_bytecode(Bytecode::Generator&) const override;
virtual Bytecode::CodeGenerationErrorOr<void> generate_bytecode_with_lhs_name(Bytecode::Generator&, Optional<DeprecatedFlyString const&> lhs_name) const;

bool has_name() const { return !m_name.is_empty(); }

Expand Down
45 changes: 36 additions & 9 deletions Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,11 @@ Bytecode::CodeGenerationErrorOr<void> AssignmentExpression::generate_bytecode(By
// d. Else,
// i. Let rref be the result of evaluating AssignmentExpression.
// ii. Let rval be ? GetValue(rref).
TRY(m_rhs->generate_bytecode(generator));
if (lhs->is_identifier()) {
TRY(generator.emit_named_evaluation_if_anonymous_function(*m_rhs, static_cast<Identifier const&>(*lhs).string()));
} else {
TRY(m_rhs->generate_bytecode(generator));
}

// e. Perform ? PutValue(lref, rval).
if (is<Identifier>(*lhs)) {
Expand Down Expand Up @@ -960,7 +964,7 @@ Bytecode::CodeGenerationErrorOr<void> FunctionDeclaration::generate_bytecode(Byt
return {};
}

Bytecode::CodeGenerationErrorOr<void> FunctionExpression::generate_bytecode(Bytecode::Generator& generator) const
Bytecode::CodeGenerationErrorOr<void> FunctionExpression::generate_bytecode_with_lhs_name(Bytecode::Generator& generator, Optional<DeprecatedFlyString const&> lhs_name) const
{
bool has_name = !name().is_empty();
Optional<Bytecode::IdentifierTableIndex> name_identifier;
Expand All @@ -972,7 +976,7 @@ Bytecode::CodeGenerationErrorOr<void> FunctionExpression::generate_bytecode(Byte
generator.emit<Bytecode::Op::CreateVariable>(*name_identifier, Bytecode::Op::EnvironmentMode::Lexical, true);
}

generator.emit_new_function(*this);
generator.emit_new_function(*this, lhs_name);

if (has_name) {
generator.emit<Bytecode::Op::SetVariable>(*name_identifier, Bytecode::Op::SetVariable::InitializationMode::Initialize, Bytecode::Op::EnvironmentMode::Lexical);
Expand All @@ -982,6 +986,11 @@ Bytecode::CodeGenerationErrorOr<void> FunctionExpression::generate_bytecode(Byte
return {};
}

Bytecode::CodeGenerationErrorOr<void> FunctionExpression::generate_bytecode(Bytecode::Generator& generator) const
{
return generate_bytecode_with_lhs_name(generator, {});
}

static Bytecode::CodeGenerationErrorOr<void> generate_object_binding_pattern_bytecode(Bytecode::Generator& generator, BindingPattern const& pattern, Bytecode::Op::SetVariable::InitializationMode initialization_mode, Bytecode::Register const& value_reg)
{
Vector<Bytecode::Register> excluded_property_names;
Expand Down Expand Up @@ -1041,7 +1050,11 @@ static Bytecode::CodeGenerationErrorOr<void> generate_object_binding_pattern_byt
Bytecode::Label { if_not_undefined_block });

generator.switch_to_basic_block(if_undefined_block);
TRY(initializer->generate_bytecode(generator));
if (auto const* lhs = name.get_pointer<NonnullRefPtr<Identifier const>>()) {
TRY(generator.emit_named_evaluation_if_anonymous_function(*initializer, (*lhs)->string()));
} else {
TRY(initializer->generate_bytecode(generator));
}
generator.emit<Bytecode::Op::Jump>().set_targets(
Bytecode::Label { if_not_undefined_block },
{});
Expand Down Expand Up @@ -1231,7 +1244,12 @@ static Bytecode::CodeGenerationErrorOr<void> generate_array_binding_pattern_byte
Bytecode::Label { value_is_not_undefined_block });

generator.switch_to_basic_block(value_is_undefined_block);
TRY(initializer->generate_bytecode(generator));

if (auto const* lhs = name.get_pointer<NonnullRefPtr<Identifier const>>()) {
TRY(generator.emit_named_evaluation_if_anonymous_function(*initializer, (*lhs)->string()));
} else {
TRY(initializer->generate_bytecode(generator));
}
generator.emit<Bytecode::Op::Jump>(Bytecode::Label { value_is_not_undefined_block });

generator.switch_to_basic_block(value_is_not_undefined_block);
Expand Down Expand Up @@ -1273,7 +1291,11 @@ Bytecode::CodeGenerationErrorOr<void> VariableDeclaration::generate_bytecode(Byt
{
for (auto& declarator : m_declarations) {
if (declarator->init()) {
TRY(declarator->init()->generate_bytecode(generator));
if (auto const* lhs = declarator->target().get_pointer<NonnullRefPtr<Identifier const>>()) {
TRY(generator.emit_named_evaluation_if_anonymous_function(*declarator->init(), (*lhs)->string()));
} else {
TRY(declarator->init()->generate_bytecode(generator));
}
TRY(assign_accumulator_to_variable_declarator(generator, declarator, *this));
} else if (m_declaration_kind != DeclarationKind::Var) {
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
Expand Down Expand Up @@ -2166,12 +2188,17 @@ Bytecode::CodeGenerationErrorOr<void> ClassDeclaration::generate_bytecode(Byteco
return {};
}

Bytecode::CodeGenerationErrorOr<void> ClassExpression::generate_bytecode(Bytecode::Generator& generator) const
Bytecode::CodeGenerationErrorOr<void> ClassExpression::generate_bytecode_with_lhs_name(Bytecode::Generator& generator, Optional<DeprecatedFlyString const&> lhs_name) const
{
generator.emit<Bytecode::Op::NewClass>(*this);
generator.emit<Bytecode::Op::NewClass>(*this, lhs_name);
return {};
}

Bytecode::CodeGenerationErrorOr<void> ClassExpression::generate_bytecode(Bytecode::Generator& generator) const
{
return generate_bytecode_with_lhs_name(generator, {});
}

Bytecode::CodeGenerationErrorOr<void> SpreadExpression::generate_bytecode(Bytecode::Generator& generator) const
{
// NOTE: All users of this should handle the behaviour of this on their own,
Expand Down Expand Up @@ -2616,7 +2643,7 @@ Bytecode::CodeGenerationErrorOr<void> MetaProperty::generate_bytecode(Bytecode::

Bytecode::CodeGenerationErrorOr<void> ClassFieldInitializerStatement::generate_bytecode(Bytecode::Generator& generator) const
{
TRY(m_expression->generate_bytecode(generator));
TRY(generator.emit_named_evaluation_if_anonymous_function(*m_expression, m_class_field_identifier_name));
generator.perform_needed_unwinds<Bytecode::Op::Return>();
generator.emit<Bytecode::Op::Return>();
return {};
Expand Down
28 changes: 25 additions & 3 deletions Userland/Libraries/LibJS/Bytecode/Generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -452,12 +452,34 @@ void Generator::pop_home_object()
m_home_objects.take_last();
}

void Generator::emit_new_function(FunctionNode const& function_node)
void Generator::emit_new_function(FunctionExpression const& function_node, Optional<DeprecatedFlyString const&> lhs_name)
{
if (m_home_objects.is_empty())
emit<Op::NewFunction>(function_node);
emit<Op::NewFunction>(function_node, lhs_name);
else
emit<Op::NewFunction>(function_node, m_home_objects.last());
emit<Op::NewFunction>(function_node, lhs_name, m_home_objects.last());
}

CodeGenerationErrorOr<void> Generator::emit_named_evaluation_if_anonymous_function(Expression const& expression, Optional<DeprecatedFlyString const&> lhs_name)
{
if (is<FunctionExpression>(expression)) {
auto const& function_expression = static_cast<FunctionExpression const&>(expression);
if (!function_expression.has_name()) {
TRY(function_expression.generate_bytecode_with_lhs_name(*this, move(lhs_name)));
return {};
}
}

if (is<ClassExpression>(expression)) {
auto const& class_expression = static_cast<ClassExpression const&>(expression);
if (!class_expression.has_name()) {
TRY(class_expression.generate_bytecode_with_lhs_name(*this, move(lhs_name)));
return {};
}
}

TRY(expression.generate_bytecode(*this));
return {};
}

}
4 changes: 3 additions & 1 deletion Userland/Libraries/LibJS/Bytecode/Generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ class Generator {

void push_home_object(Register);
void pop_home_object();
void emit_new_function(JS::FunctionNode const&);
void emit_new_function(JS::FunctionExpression const&, Optional<DeprecatedFlyString const&> lhs_name);

CodeGenerationErrorOr<void> emit_named_evaluation_if_anonymous_function(Expression const&, Optional<DeprecatedFlyString const&> lhs_name);

void begin_continuable_scope(Label continue_target, Vector<DeprecatedFlyString> const& language_label_set);
void end_continuable_scope();
Expand Down
32 changes: 27 additions & 5 deletions Userland/Libraries/LibJS/Bytecode/Op.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,13 @@ ThrowCompletionOr<void> SuperCall::execute_impl(Bytecode::Interpreter& interpret
ThrowCompletionOr<void> NewFunction::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
interpreter.accumulator() = ECMAScriptFunctionObject::create(interpreter.realm(), m_function_node.name(), m_function_node.source_text(), m_function_node.body(), m_function_node.parameters(), m_function_node.function_length(), vm.lexical_environment(), vm.running_execution_context().private_environment, m_function_node.kind(), m_function_node.is_strict_mode(), m_function_node.might_need_arguments_object(), m_function_node.contains_direct_call_to_eval(), m_function_node.is_arrow_function());

if (!m_function_node.has_name()) {
interpreter.accumulator() = m_function_node.instantiate_ordinary_function_expression(vm, m_lhs_name.value_or({}));
} else {
interpreter.accumulator() = ECMAScriptFunctionObject::create(interpreter.realm(), m_function_node.name(), m_function_node.source_text(), m_function_node.body(), m_function_node.parameters(), m_function_node.function_length(), vm.lexical_environment(), vm.running_execution_context().private_environment, m_function_node.kind(), m_function_node.is_strict_mode(), m_function_node.might_need_arguments_object(), m_function_node.contains_direct_call_to_eval(), m_function_node.is_arrow_function());
}

if (m_home_object.has_value()) {
auto home_object_value = interpreter.reg(m_home_object.value());
static_cast<ECMAScriptFunctionObject&>(interpreter.accumulator().as_function()).set_home_object(&home_object_value.as_object());
Expand Down Expand Up @@ -1105,7 +1111,13 @@ ThrowCompletionOr<void> NewClass::execute_impl(Bytecode::Interpreter& interprete
auto scope = interpreter.ast_interpreter_scope(interpreter.realm());
auto& ast_interpreter = scope.interpreter();

auto* class_object = TRY(m_class_expression.class_definition_evaluation(ast_interpreter, name, name.is_null() ? ""sv : name));
ECMAScriptFunctionObject* class_object = nullptr;

if (!m_class_expression.has_name() && m_lhs_name.has_value())
class_object = TRY(m_class_expression.class_definition_evaluation(ast_interpreter, {}, m_lhs_name.value()));
else
class_object = TRY(m_class_expression.class_definition_evaluation(ast_interpreter, name, name.is_null() ? ""sv : name));

class_object->set_source_text(m_class_expression.source_text());

interpreter.accumulator() = class_object;
Expand Down Expand Up @@ -1355,15 +1367,25 @@ DeprecatedString SuperCall::to_deprecated_string_impl(Bytecode::Executable const

DeprecatedString NewFunction::to_deprecated_string_impl(Bytecode::Executable const&) const
{
StringBuilder builder;
builder.append("NewFunction"sv);
if (m_function_node.has_name())
builder.appendff(" name:{}"sv, m_function_node.name());
if (m_lhs_name.has_value())
builder.appendff(" lhs_name:{}"sv, m_lhs_name.value());
if (m_home_object.has_value())
return DeprecatedString::formatted("NewFunction home_object:{}", m_home_object.value());
return "NewFunction"sv;
builder.appendff(" home_object:{}"sv, m_home_object.value());
return builder.to_deprecated_string();
}

DeprecatedString NewClass::to_deprecated_string_impl(Bytecode::Executable const&) const
{
StringBuilder builder;
auto name = m_class_expression.name();
return DeprecatedString::formatted("NewClass '{}'", name.is_null() ? ""sv : name);
builder.appendff("NewClass '{}'"sv, name.is_null() ? ""sv : name);
if (m_lhs_name.has_value())
builder.appendff(" lhs_name:{}"sv, m_lhs_name.value());
return builder.to_deprecated_string();
}

DeprecatedString Return::to_deprecated_string_impl(Bytecode::Executable const&) const
Expand Down
14 changes: 11 additions & 3 deletions Userland/Libraries/LibJS/Bytecode/Op.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
#include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/ValueTraits.h>

namespace JS {
class FunctionExpression;
}

namespace JS::Bytecode::Op {

class Load final : public Instruction {
Expand Down Expand Up @@ -798,9 +802,10 @@ class SuperCall : public Instruction {

class NewClass final : public Instruction {
public:
explicit NewClass(ClassExpression const& class_expression)
explicit NewClass(ClassExpression const& class_expression, Optional<DeprecatedFlyString const&> lhs_name)
: Instruction(Type::NewClass)
, m_class_expression(class_expression)
, m_lhs_name(lhs_name.has_value() ? *lhs_name : Optional<DeprecatedFlyString> {})
{
}

Expand All @@ -811,13 +816,15 @@ class NewClass final : public Instruction {

private:
ClassExpression const& m_class_expression;
Optional<DeprecatedFlyString> m_lhs_name;
};

class NewFunction final : public Instruction {
public:
explicit NewFunction(FunctionNode const& function_node, Optional<Register> home_object = {})
explicit NewFunction(FunctionExpression const& function_node, Optional<DeprecatedFlyString const&> lhs_name, Optional<Register> home_object = {})
: Instruction(Type::NewFunction)
, m_function_node(function_node)
, m_lhs_name(lhs_name.has_value() ? *lhs_name : Optional<DeprecatedFlyString> {})
, m_home_object(move(home_object))
{
}
Expand All @@ -828,7 +835,8 @@ class NewFunction final : public Instruction {
void replace_references_impl(Register, Register);

private:
FunctionNode const& m_function_node;
FunctionExpression const& m_function_node;
Optional<DeprecatedFlyString> m_lhs_name;
Optional<Register> m_home_object;
};

Expand Down
2 changes: 1 addition & 1 deletion Userland/Libraries/LibJS/Runtime/VM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ ThrowCompletionOr<Value> VM::named_evaluation_if_anonymous_function(ASTNode cons
if (is<FunctionExpression>(expression)) {
auto& function = static_cast<FunctionExpression const&>(expression);
if (!function.has_name()) {
return function.instantiate_ordinary_function_expression(interpreter(), name);
return function.instantiate_ordinary_function_expression(*this, name);
}
} else if (is<ClassExpression>(expression)) {
auto& class_expression = static_cast<ClassExpression const&>(expression);
Expand Down

0 comments on commit 6453d91

Please sign in to comment.