LibJS: Update bytecode generator to use local variables

- Update ECMAScriptFunctionObject::function_declaration_instantiation
  to initialize local variables
- Introduce GetLocal, SetLocal, TypeofLocal that will be used to
  operate on local variables.
- Update bytecode generator to emit instructions for local variables
This commit is contained in:
Aliaksandr Kalenik 2023-07-05 02:17:10 +02:00 committed by Andreas Kling
parent 0daff637e2
commit ae3a7fd4b8
8 changed files with 170 additions and 37 deletions

View file

@ -221,7 +221,11 @@ Bytecode::CodeGenerationErrorOr<void> UnaryExpression::generate_bytecode(Bytecod
case UnaryOp::Typeof:
if (is<Identifier>(*m_lhs)) {
auto& identifier = static_cast<Identifier const&>(*m_lhs);
generator.emit<Bytecode::Op::TypeofVariable>(generator.intern_identifier(identifier.string()));
if (identifier.is_local()) {
generator.emit<Bytecode::Op::TypeofLocal>(identifier.local_variable_index());
} else {
generator.emit<Bytecode::Op::TypeofVariable>(generator.intern_identifier(identifier.string()));
}
break;
}
@ -291,7 +295,11 @@ Bytecode::CodeGenerationErrorOr<void> RegExpLiteral::generate_bytecode(Bytecode:
Bytecode::CodeGenerationErrorOr<void> Identifier::generate_bytecode(Bytecode::Generator& generator) const
{
generator.emit<Bytecode::Op::GetVariable>(generator.intern_identifier(m_string));
if (is_local()) {
generator.emit<Bytecode::Op::GetLocal>(local_variable_index());
} else {
generator.emit<Bytecode::Op::GetVariable>(generator.intern_identifier(m_string));
}
return {};
}
@ -415,7 +423,7 @@ Bytecode::CodeGenerationErrorOr<void> AssignmentExpression::generate_bytecode(By
// e. Perform ? PutValue(lref, rval).
if (is<Identifier>(*lhs)) {
auto& identifier = static_cast<Identifier const&>(*lhs);
generator.emit<Bytecode::Op::SetVariable>(generator.intern_identifier(identifier.string()));
generator.emit_set_variable(identifier);
} else if (is<MemberExpression>(*lhs)) {
auto& expression = static_cast<MemberExpression const&>(*lhs);
@ -1043,13 +1051,15 @@ static Bytecode::CodeGenerationErrorOr<void> generate_object_binding_pattern_byt
if (is_rest) {
VERIFY(!initializer);
if (name.has<NonnullRefPtr<Identifier const>>()) {
auto identifier = name.get<NonnullRefPtr<Identifier const>>()->string();
auto interned_identifier = generator.intern_identifier(identifier);
auto identifier = name.get<NonnullRefPtr<Identifier const>>();
auto interned_identifier = generator.intern_identifier(identifier->string());
generator.emit_with_extra_register_slots<Bytecode::Op::CopyObjectExcludingProperties>(excluded_property_names.size(), value_reg, excluded_property_names);
if (create_variables)
if (create_variables) {
VERIFY(!identifier->is_local());
generator.emit<Bytecode::Op::CreateVariable>(interned_identifier, Bytecode::Op::EnvironmentMode::Lexical, false);
generator.emit<Bytecode::Op::SetVariable>(interned_identifier, initialization_mode);
}
generator.emit_set_variable(*identifier, initialization_mode);
return {};
}
@ -1126,19 +1136,19 @@ static Bytecode::CodeGenerationErrorOr<void> generate_object_binding_pattern_byt
};
}
auto& identifier = name.get<NonnullRefPtr<Identifier const>>()->string();
auto identifier_ref = generator.intern_identifier(identifier);
auto const& identifier = *name.get<NonnullRefPtr<Identifier const>>();
auto identifier_ref = generator.intern_identifier(identifier.string());
if (create_variables)
generator.emit<Bytecode::Op::CreateVariable>(identifier_ref, Bytecode::Op::EnvironmentMode::Lexical, false);
generator.emit<Bytecode::Op::SetVariable>(identifier_ref, initialization_mode);
generator.emit_set_variable(identifier, initialization_mode);
} else if (alias.has<NonnullRefPtr<MemberExpression const>>()) {
TRY(generator.emit_store_to_reference(alias.get<NonnullRefPtr<MemberExpression const>>()));
} else {
auto& identifier = alias.get<NonnullRefPtr<Identifier const>>()->string();
auto identifier_ref = generator.intern_identifier(identifier);
auto const& identifier = *alias.get<NonnullRefPtr<Identifier const>>();
auto identifier_ref = generator.intern_identifier(identifier.string());
if (create_variables)
generator.emit<Bytecode::Op::CreateVariable>(identifier_ref, Bytecode::Op::EnvironmentMode::Lexical, false);
generator.emit<Bytecode::Op::SetVariable>(identifier_ref, initialization_mode);
generator.emit_set_variable(identifier, initialization_mode);
}
}
return {};
@ -1189,7 +1199,7 @@ static Bytecode::CodeGenerationErrorOr<void> generate_array_binding_pattern_byte
auto interned_index = generator.intern_identifier(identifier->string());
if (create_variables)
generator.emit<Bytecode::Op::CreateVariable>(interned_index, Bytecode::Op::EnvironmentMode::Lexical, false);
generator.emit<Bytecode::Op::SetVariable>(interned_index, initialization_mode);
generator.emit_set_variable(*identifier, initialization_mode);
return {};
},
[&](NonnullRefPtr<BindingPattern const> const& pattern) -> Bytecode::CodeGenerationErrorOr<void> {
@ -1355,7 +1365,7 @@ static Bytecode::CodeGenerationErrorOr<void> assign_accumulator_to_variable_decl
return declarator.target().visit(
[&](NonnullRefPtr<Identifier const> const& id) -> Bytecode::CodeGenerationErrorOr<void> {
generator.emit<Bytecode::Op::SetVariable>(generator.intern_identifier(id->string()), initialization_mode);
generator.emit_set_variable(*id, initialization_mode);
return {};
},
[&](NonnullRefPtr<BindingPattern const> const& pattern) -> Bytecode::CodeGenerationErrorOr<void> {
@ -2309,7 +2319,7 @@ Bytecode::CodeGenerationErrorOr<void> ClassDeclaration::generate_bytecode(Byteco
generator.emit<Bytecode::Op::Store>(accumulator_backup_reg);
TRY(m_class_expression->generate_bytecode(generator));
generator.emit<Bytecode::Op::SetVariable>(generator.intern_identifier(m_class_expression.ptr()->name()), Bytecode::Op::SetVariable::InitializationMode::Initialize);
generator.emit_set_variable(*m_class_expression.ptr()->m_name, Bytecode::Op::SetVariable::InitializationMode::Initialize);
generator.emit<Bytecode::Op::Load>(accumulator_backup_reg);
return {};
@ -2464,9 +2474,10 @@ static Bytecode::CodeGenerationErrorOr<ForInOfHeadEvaluationResult> for_in_of_he
auto& variable = variable_declaration.declarations().first();
if (variable->init()) {
VERIFY(variable->target().has<NonnullRefPtr<Identifier const>>());
auto binding_id = generator.intern_identifier(variable->target().get<NonnullRefPtr<Identifier const>>()->string());
TRY(generator.emit_named_evaluation_if_anonymous_function(*variable->init(), binding_id));
generator.emit<Bytecode::Op::SetVariable>(binding_id);
auto identifier = variable->target().get<NonnullRefPtr<Identifier const>>();
auto identifier_table_ref = generator.intern_identifier(identifier->string());
TRY(generator.emit_named_evaluation_if_anonymous_function(*variable->init(), identifier_table_ref));
generator.emit_set_variable(*identifier);
}
} else {
// 1. Let oldEnv be the running execution context's LexicalEnvironment.
@ -2652,11 +2663,10 @@ static Bytecode::CodeGenerationErrorOr<void> for_in_of_body_evaluation(Bytecode:
if (!destructuring) {
// 1. Assert: lhs binds a single name.
// 2. Let lhsName be the sole element of BoundNames of lhs.
auto lhs_name = variable_declaration.declarations().first()->target().get<NonnullRefPtr<Identifier const>>()->string();
auto lhs_name = variable_declaration.declarations().first()->target().get<NonnullRefPtr<Identifier const>>();
// 3. Let lhsRef be ! ResolveBinding(lhsName).
// NOTE: We're skipping all the completion stuff that the spec does, as the unwinding mechanism will take case of doing that.
auto identifier = generator.intern_identifier(lhs_name);
generator.emit<Bytecode::Op::SetVariable>(identifier, Bytecode::Op::SetVariable::InitializationMode::Initialize, Bytecode::Op::EnvironmentMode::Lexical);
generator.emit_set_variable(*lhs_name, Bytecode::Op::SetVariable::InitializationMode::Initialize, Bytecode::Op::EnvironmentMode::Lexical);
}
}
// i. If destructuring is false, then

View file

@ -141,7 +141,7 @@ CodeGenerationErrorOr<void> Generator::emit_load_from_reference(JS::ASTNode cons
{
if (is<Identifier>(node)) {
auto& identifier = static_cast<Identifier const&>(node);
emit<Bytecode::Op::GetVariable>(intern_identifier(identifier.string()));
TRY(identifier.generate_bytecode(*this));
return {};
}
if (is<MemberExpression>(node)) {
@ -217,7 +217,7 @@ CodeGenerationErrorOr<void> Generator::emit_store_to_reference(JS::ASTNode const
{
if (is<Identifier>(node)) {
auto& identifier = static_cast<Identifier const&>(node);
emit<Bytecode::Op::SetVariable>(intern_identifier(identifier.string()));
emit_set_variable(identifier);
return {};
}
if (is<MemberExpression>(node)) {
@ -306,6 +306,15 @@ CodeGenerationErrorOr<void> Generator::emit_delete_reference(JS::ASTNode const&
return {};
}
void Generator::emit_set_variable(JS::Identifier const& identifier, Bytecode::Op::SetVariable::InitializationMode initialization_mode, Bytecode::Op::EnvironmentMode mode)
{
if (identifier.is_local()) {
emit<Bytecode::Op::SetLocal>(identifier.local_variable_index());
} else {
emit<Bytecode::Op::SetVariable>(intern_identifier(identifier.string()), initialization_mode, mode);
}
}
void Generator::generate_break()
{
bool last_was_finally = false;

View file

@ -83,6 +83,8 @@ public:
CodeGenerationErrorOr<void> emit_store_to_reference(JS::ASTNode const&);
CodeGenerationErrorOr<void> emit_delete_reference(JS::ASTNode const&);
void emit_set_variable(JS::Identifier const& identifier, Bytecode::Op::SetVariable::InitializationMode initialization_mode = Bytecode::Op::SetVariable::InitializationMode::Set, Bytecode::Op::EnvironmentMode mode = Bytecode::Op::EnvironmentMode::Lexical);
void push_home_object(Register);
void pop_home_object();
void emit_new_function(JS::FunctionExpression const&, Optional<IdentifierTableIndex> lhs_name);

View file

@ -41,6 +41,7 @@
O(GetObjectPropertyIterator) \
O(GetPrivateById) \
O(GetVariable) \
O(GetLocal) \
O(GreaterThan) \
O(GreaterThanEquals) \
O(HasPrivateId) \
@ -87,6 +88,7 @@
O(RightShift) \
O(ScheduleJump) \
O(SetVariable) \
O(SetLocal) \
O(Store) \
O(StrictlyEquals) \
O(StrictlyInequals) \
@ -98,6 +100,7 @@
O(ToNumeric) \
O(Typeof) \
O(TypeofVariable) \
O(TypeofLocal) \
O(UnaryMinus) \
O(UnaryPlus) \
O(UnsignedRightShift) \

View file

@ -422,6 +422,17 @@ ThrowCompletionOr<void> GetVariable::execute_impl(Bytecode::Interpreter& interpr
return {};
}
ThrowCompletionOr<void> GetLocal::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
if (vm.running_execution_context().local_variables[m_index].is_empty()) {
auto const& variable_name = vm.running_execution_context().function->local_variables_names()[m_index];
return interpreter.vm().throw_completion<ReferenceError>(ErrorType::BindingNotInitialized, variable_name);
}
interpreter.accumulator() = vm.running_execution_context().local_variables[m_index];
return {};
}
ThrowCompletionOr<void> DeleteVariable::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
@ -506,6 +517,12 @@ ThrowCompletionOr<void> SetVariable::execute_impl(Bytecode::Interpreter& interpr
return {};
}
ThrowCompletionOr<void> SetLocal::execute_impl(Bytecode::Interpreter& interpreter) const
{
interpreter.vm().running_execution_context().local_variables[m_index] = interpreter.accumulator();
return {};
}
ThrowCompletionOr<void> GetById::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
@ -1240,6 +1257,14 @@ ThrowCompletionOr<void> TypeofVariable::execute_impl(Bytecode::Interpreter& inte
return {};
}
ThrowCompletionOr<void> TypeofLocal::execute_impl(Bytecode::Interpreter& interpreter) const
{
auto& vm = interpreter.vm();
auto const& value = vm.running_execution_context().local_variables[m_index];
interpreter.accumulator() = MUST_OR_THROW_OOM(PrimitiveString::create(vm, value.typeof()));
return {};
}
ThrowCompletionOr<void> ToNumeric::execute_impl(Bytecode::Interpreter& interpreter) const
{
interpreter.accumulator() = TRY(interpreter.accumulator().to_numeric(interpreter.vm()));
@ -1335,6 +1360,11 @@ DeprecatedString GetVariable::to_deprecated_string_impl(Bytecode::Executable con
return DeprecatedString::formatted("GetVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier));
}
DeprecatedString GetLocal::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return DeprecatedString::formatted("GetLocal {}", m_index);
}
DeprecatedString DeleteVariable::to_deprecated_string_impl(Bytecode::Executable const& executable) const
{
return DeprecatedString::formatted("DeleteVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier));
@ -1365,6 +1395,11 @@ DeprecatedString SetVariable::to_deprecated_string_impl(Bytecode::Executable con
return DeprecatedString::formatted("SetVariable env:{} init:{} {} ({})", mode_string, initialization_mode_name, m_identifier, executable.identifier_table->get(m_identifier));
}
DeprecatedString SetLocal::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return DeprecatedString::formatted("SetLocal {}", m_index);
}
DeprecatedString PutById::to_deprecated_string_impl(Bytecode::Executable const& executable) const
{
auto kind = m_kind == PropertyKind::Getter
@ -1652,6 +1687,11 @@ DeprecatedString TypeofVariable::to_deprecated_string_impl(Bytecode::Executable
return DeprecatedString::formatted("TypeofVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier));
}
DeprecatedString TypeofLocal::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return DeprecatedString::formatted("TypeofLocal {}", m_index);
}
DeprecatedString ToNumeric::to_deprecated_string_impl(Bytecode::Executable const&) const
{
return "ToNumeric"sv;

View file

@ -484,6 +484,25 @@ private:
InitializationMode m_initialization_mode { InitializationMode::Set };
};
class SetLocal final : public Instruction {
public:
explicit SetLocal(size_t index)
: Instruction(Type::SetLocal)
, m_index(index)
{
}
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) { }
size_t index() const { return m_index; }
private:
size_t m_index;
};
class GetVariable final : public Instruction {
public:
explicit GetVariable(IdentifierTableIndex identifier)
@ -505,6 +524,25 @@ private:
Optional<EnvironmentCoordinate> mutable m_cached_environment_coordinate;
};
class GetLocal final : public Instruction {
public:
explicit GetLocal(size_t index)
: Instruction(Type::GetLocal)
, m_index(index)
{
}
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) { }
size_t index() const { return m_index; }
private:
size_t m_index;
};
class DeleteVariable final : public Instruction {
public:
explicit DeleteVariable(IdentifierTableIndex identifier)
@ -1338,6 +1376,23 @@ private:
IdentifierTableIndex m_identifier;
};
class TypeofLocal final : public Instruction {
public:
explicit TypeofLocal(size_t index)
: Instruction(Type::TypeofLocal)
, m_index(index)
{
}
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) { }
private:
size_t m_index;
};
}
namespace JS::Bytecode {

View file

@ -184,6 +184,7 @@ class HeapBlock;
struct ImportEntry;
class ImportStatement;
class Interpreter;
class Identifier;
class Intrinsics;
struct IteratorRecord;
class MetaProperty;

View file

@ -503,10 +503,14 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
if (scope_body) {
// NOTE: Due to the use of MUST with `create_mutable_binding` and `initialize_binding` below,
// an exception should not result from `for_each_var_declared_name`.
MUST(scope_body->for_each_var_declared_name([&](auto const& name) {
if (!parameter_names.contains(name) && instantiated_var_names.set(name) == AK::HashSetResult::InsertedNewEntry) {
MUST(environment->create_mutable_binding(vm, name, false));
MUST(environment->initialize_binding(vm, name, js_undefined(), Environment::InitializeBindingHint::Normal));
MUST(scope_body->for_each_var_declared_identifier([&](auto const& id) {
if (!parameter_names.contains(id.string()) && instantiated_var_names.set(id.string()) == AK::HashSetResult::InsertedNewEntry) {
if (vm.bytecode_interpreter_if_exists() && id.is_local()) {
callee_context.local_variables[id.local_variable_index()] = js_undefined();
} else {
MUST(environment->create_mutable_binding(vm, id.string(), false));
MUST(environment->initialize_binding(vm, id.string(), js_undefined(), Environment::InitializeBindingHint::Normal));
}
}
}));
}
@ -518,18 +522,23 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
if (scope_body) {
// NOTE: Due to the use of MUST with `create_mutable_binding`, `get_binding_value` and `initialize_binding` below,
// an exception should not result from `for_each_var_declared_name`.
MUST(scope_body->for_each_var_declared_name([&](auto const& name) {
if (instantiated_var_names.set(name) != AK::HashSetResult::InsertedNewEntry)
MUST(scope_body->for_each_var_declared_identifier([&](auto const& id) {
if (instantiated_var_names.set(id.string()) != AK::HashSetResult::InsertedNewEntry)
return;
MUST(var_environment->create_mutable_binding(vm, name, false));
MUST(var_environment->create_mutable_binding(vm, id.string(), false));
Value initial_value;
if (!parameter_names.contains(name) || function_names.contains(name))
if (!parameter_names.contains(id.string()) || function_names.contains(id.string()))
initial_value = js_undefined();
else
initial_value = MUST(environment->get_binding_value(vm, name, false));
initial_value = MUST(environment->get_binding_value(vm, id.string(), false));
MUST(var_environment->initialize_binding(vm, name, initial_value, Environment::InitializeBindingHint::Normal));
if (vm.bytecode_interpreter_if_exists() && id.is_local()) {
// NOTE: Local variables are supported only in bytecode interpreter
callee_context.local_variables[id.local_variable_index()] = initial_value;
} else {
MUST(var_environment->initialize_binding(vm, id.string(), initial_value, Environment::InitializeBindingHint::Normal));
}
}));
}
}
@ -586,11 +595,15 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
MUST(scope_body->for_each_lexically_scoped_declaration([&](Declaration const& declaration) {
// NOTE: Due to the use of MUST with `create_immutable_binding` and `create_mutable_binding` below,
// an exception should not result from `for_each_bound_name`.
MUST(declaration.for_each_bound_name([&](auto const& name) {
MUST(declaration.for_each_bound_identifier([&](auto const& id) {
if (vm.bytecode_interpreter_if_exists() && id.is_local()) {
// NOTE: Local variables are supported only in bytecode interpreter
return;
}
if (declaration.is_constant_declaration())
MUST(lex_environment->create_immutable_binding(vm, name, true));
MUST(lex_environment->create_immutable_binding(vm, id.string(), true));
else
MUST(lex_environment->create_mutable_binding(vm, name, false));
MUST(lex_environment->create_mutable_binding(vm, id.string(), false));
}));
}));