From e46b217e423e5c060fc7c23c0a3129d478f46efe Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sun, 4 Feb 2024 08:00:54 +0100 Subject: [PATCH] LibJS/Bytecode: Move to a new bytecode format This patch moves us away from the accumulator-based bytecode format to one with explicit source and destination registers. The new format has multiple benefits: - ~25% faster on the Kraken and Octane benchmarks :^) - Fewer instructions to accomplish the same thing - Much easier for humans to read(!) Because this change requires a fundamental shift in how bytecode is generated, it is quite comprehensive. Main implementation mechanism: generate_bytecode() virtual function now takes an optional "preferred dst" operand, which allows callers to communicate when they have an operand that would be optimal for the result to go into. It also returns an optional "actual dst" operand, which is where the completion value (if any) of the AST node is stored after the node has "executed". One thing of note that's new: because instructions can now take locals as operands, this means we got rid of the GetLocal instruction. A side-effect of that is we have to think about the temporal deadzone (TDZ) a bit differently for locals (GetLocal would previously check for empty values and interpret that as a TDZ access and throw). We now insert special ThrowIfTDZ instructions in places where a local access may be in the TDZ, to maintain the correct behavior. There are a number of progressions and regressions from this test: A number of async generator tests have been accidentally fixed while converting the implementation to the new bytecode format. It didn't seem useful to preserve bugs in the original code when converting it. Some "does eval() return the correct completion value" tests have regressed, in particular ones related to propagating the appropriate completion after control flow statements like continue and break. These are all fairly obscure issues, and I believe we can continue working on them separately. The net test262 result is a progression though. :^) --- Userland/Libraries/LibJS/AST.cpp | 2 +- .../Libraries/LibJS/Bytecode/ASTCodegen.cpp | 1855 +++++++++-------- .../LibJS/Bytecode/CommonImplementations.h | 4 +- .../Libraries/LibJS/Bytecode/Generator.cpp | 324 +-- Userland/Libraries/LibJS/Bytecode/Generator.h | 69 +- .../Libraries/LibJS/Bytecode/Instruction.h | 13 +- .../Libraries/LibJS/Bytecode/Interpreter.cpp | 915 ++++---- .../Libraries/LibJS/Bytecode/Interpreter.h | 4 +- Userland/Libraries/LibJS/Bytecode/Op.h | 753 +++++-- .../LibJS/Runtime/AbstractOperations.cpp | 2 +- .../Runtime/ECMAScriptFunctionObject.cpp | 6 +- .../Libraries/LibJS/Runtime/ShadowRealm.cpp | 2 +- Userland/Libraries/LibJS/Runtime/VM.cpp | 2 +- Userland/Libraries/LibJS/SourceTextModule.cpp | 2 +- 14 files changed, 2311 insertions(+), 1642 deletions(-) diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 3efc5d7670..b6d8e878a9 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -239,7 +239,7 @@ ThrowCompletionOr ClassField::class_element_evaluation // FIXME: A potential optimization is not creating the functions here since these are never directly accessible. auto function_code = create_ast_node(m_initializer->source_range(), copy_initializer.release_nonnull(), name); - initializer = make_handle(*ECMAScriptFunctionObject::create(realm, ByteString::empty(), ByteString::empty(), *function_code, {}, 0, {}, vm.lexical_environment(), vm.running_execution_context().private_environment, FunctionKind::Normal, true, false, m_contains_direct_call_to_eval, false, property_key_or_private_name)); + initializer = make_handle(*ECMAScriptFunctionObject::create(realm, "field", ByteString::empty(), *function_code, {}, 0, {}, vm.lexical_environment(), vm.running_execution_context().private_environment, FunctionKind::Normal, true, false, m_contains_direct_call_to_eval, false, property_key_or_private_name)); initializer->make_method(target); } diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 17acb46770..0266c87c9f 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -7,8 +7,6 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#define FIXME_NEWBC (void) - #include #include #include @@ -21,6 +19,13 @@ namespace JS { +static Bytecode::Operand choose_dst(Bytecode::Generator& generator, Optional const& preferred_dst) +{ + if (preferred_dst.has_value()) + return preferred_dst.value(); + return Bytecode::Operand(generator.allocate_register()); +} + Bytecode::CodeGenerationErrorOr> ASTNode::generate_bytecode(Bytecode::Generator&, [[maybe_unused]] Optional preferred_dst) const { return Bytecode::CodeGenerationError { @@ -45,8 +50,11 @@ Bytecode::CodeGenerationErrorOr> ScopeNode::generate // FunctionDeclarationInstantiation is handled by the C++ AO. } + Optional last_result; for (auto& child : children()) { - FIXME_NEWBC TRY(child->generate_bytecode(generator)); + auto result = TRY(child->generate_bytecode(generator)); + if (result.has_value()) + last_result = result; if (generator.is_current_block_terminated()) break; } @@ -54,7 +62,7 @@ Bytecode::CodeGenerationErrorOr> ScopeNode::generate if (did_create_lexical_environment) generator.end_variable_scope(); - return Optional {}; + return last_result; } Bytecode::CodeGenerationErrorOr> EmptyStatement::generate_bytecode(Bytecode::Generator&, [[maybe_unused]] Optional preferred_dst) const @@ -68,99 +76,101 @@ Bytecode::CodeGenerationErrorOr> ExpressionStatement return m_expression->generate_bytecode(generator); } -Bytecode::CodeGenerationErrorOr> BinaryExpression::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> BinaryExpression::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); if (m_op == BinaryOp::In && is(*m_lhs)) { auto const& private_identifier = static_cast(*m_lhs).string(); - FIXME_NEWBC TRY(m_rhs->generate_bytecode(generator)); - generator.emit(generator.intern_identifier(private_identifier)); - return Optional {}; + auto base = TRY(m_rhs->generate_bytecode(generator)).value(); + auto dst = choose_dst(generator, preferred_dst); + generator.emit(dst, base, generator.intern_identifier(private_identifier)); + return dst; } - FIXME_NEWBC TRY(m_lhs->generate_bytecode(generator)); - auto lhs_reg = generator.allocate_register(); - generator.emit(lhs_reg); - - FIXME_NEWBC TRY(m_rhs->generate_bytecode(generator)); + auto lhs = TRY(m_lhs->generate_bytecode(generator)).value(); + auto rhs = TRY(m_rhs->generate_bytecode(generator)).value(); + auto dst = choose_dst(generator, preferred_dst); switch (m_op) { case BinaryOp::Addition: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::Subtraction: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::Multiplication: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::Division: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::Modulo: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::Exponentiation: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::GreaterThan: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::GreaterThanEquals: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::LessThan: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::LessThanEquals: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::LooselyInequals: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::LooselyEquals: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::StrictlyInequals: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::StrictlyEquals: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::BitwiseAnd: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::BitwiseOr: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::BitwiseXor: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::LeftShift: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::RightShift: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::UnsignedRightShift: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::In: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case BinaryOp::InstanceOf: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; default: VERIFY_NOT_REACHED(); } - return Optional {}; + return dst; } -Bytecode::CodeGenerationErrorOr> LogicalExpression::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> LogicalExpression::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - FIXME_NEWBC TRY(m_lhs->generate_bytecode(generator)); + auto dst = choose_dst(generator, preferred_dst); + auto lhs = TRY(m_lhs->generate_bytecode(generator, preferred_dst)).value(); + // FIXME: Only mov lhs into dst in case lhs is the value taken. + generator.emit(dst, lhs); // lhs // jump op (true) end (false) rhs @@ -173,17 +183,20 @@ Bytecode::CodeGenerationErrorOr> LogicalExpression:: switch (m_op) { case LogicalOp::And: - generator.emit( + generator.emit( + lhs, Bytecode::Label { rhs_block }, Bytecode::Label { end_block }); break; case LogicalOp::Or: - generator.emit( + generator.emit( + lhs, Bytecode::Label { end_block }, Bytecode::Label { rhs_block }); break; case LogicalOp::NullishCoalescing: generator.emit( + lhs, Bytecode::Label { rhs_block }, Bytecode::Label { end_block }); break; @@ -192,83 +205,81 @@ Bytecode::CodeGenerationErrorOr> LogicalExpression:: } generator.switch_to_basic_block(rhs_block); - FIXME_NEWBC TRY(m_rhs->generate_bytecode(generator)); + auto rhs = TRY(m_rhs->generate_bytecode(generator)).value(); + generator.emit(dst, rhs); generator.emit(Bytecode::Label { end_block }); generator.switch_to_basic_block(end_block); - return Optional {}; + return dst; } -Bytecode::CodeGenerationErrorOr> UnaryExpression::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> UnaryExpression::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); if (m_op == UnaryOp::Delete) return generator.emit_delete_reference(m_lhs); + Optional src; // Typeof needs some special handling for when the LHS is an Identifier. Namely, it shouldn't throw on unresolvable references, but instead return "undefined". if (m_op != UnaryOp::Typeof) - FIXME_NEWBC TRY(m_lhs->generate_bytecode(generator)); + src = TRY(m_lhs->generate_bytecode(generator)).value(); + + auto dst = choose_dst(generator, preferred_dst); switch (m_op) { case UnaryOp::BitwiseNot: - generator.emit(); + generator.emit(dst, *src); break; case UnaryOp::Not: - generator.emit(); + generator.emit(dst, *src); break; case UnaryOp::Plus: - generator.emit(); + generator.emit(dst, *src); break; case UnaryOp::Minus: - generator.emit(); + generator.emit(dst, *src); break; case UnaryOp::Typeof: if (is(*m_lhs)) { auto& identifier = static_cast(*m_lhs); - if (identifier.is_local()) { - generator.emit(identifier.local_variable_index()); - } else { - generator.emit(generator.intern_identifier(identifier.string())); + if (!identifier.is_local()) { + generator.emit(dst, generator.intern_identifier(identifier.string())); + break; } - break; } - FIXME_NEWBC TRY(m_lhs->generate_bytecode(generator)); - generator.emit(); + src = TRY(m_lhs->generate_bytecode(generator)).value(); + generator.emit(dst, *src); break; case UnaryOp::Void: - generator.emit(js_undefined()); - break; + return generator.add_constant(js_undefined()); case UnaryOp::Delete: // Delete is implemented above. default: VERIFY_NOT_REACHED(); } - return Optional {}; + return dst; } Bytecode::CodeGenerationErrorOr> NumericLiteral::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - generator.emit(m_value); - return Optional {}; + return generator.add_constant(Value(m_value), Bytecode::Generator::DeduplicateConstant::No); } Bytecode::CodeGenerationErrorOr> BooleanLiteral::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - generator.emit(Value(m_value)); - return Optional {}; + return generator.add_constant(Value(m_value)); } Bytecode::CodeGenerationErrorOr> NullLiteral::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - generator.emit(js_null()); - return Optional {}; + return generator.add_constant(js_null()); } -Bytecode::CodeGenerationErrorOr> BigIntLiteral::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> BigIntLiteral::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); // 1. Return the NumericValue of NumericLiteral as defined in 12.8.3. @@ -283,18 +294,20 @@ Bytecode::CodeGenerationErrorOr> BigIntLiteral::gene return MUST(Crypto::SignedBigInteger::from_base(10, m_value.substring(0, m_value.length() - 1))); }(); - generator.emit(integer); - return Optional {}; + auto dst = choose_dst(generator, preferred_dst); + generator.emit(dst, integer); + return dst; } Bytecode::CodeGenerationErrorOr> StringLiteral::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - generator.emit(generator.intern_string(m_value)); - return Optional {}; + auto dst = choose_dst(generator, preferred_dst); + generator.emit(dst, generator.intern_string(m_value)); + return dst; } -Bytecode::CodeGenerationErrorOr> RegExpLiteral::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> RegExpLiteral::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); auto source_index = generator.intern_string(m_pattern); @@ -304,29 +317,38 @@ Bytecode::CodeGenerationErrorOr> RegExpLiteral::gene .pattern = m_parsed_pattern, .flags = m_parsed_flags, }); - generator.emit(source_index, flags_index, regex_index); - return Optional {}; + auto dst = choose_dst(generator, preferred_dst); + generator.emit(dst, source_index, flags_index, regex_index); + return dst; } -Bytecode::CodeGenerationErrorOr> Identifier::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> Identifier::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - if (is_global()) { - generator.emit(generator.intern_identifier(m_string), generator.next_global_variable_cache()); - } else if (is_local()) { - generator.emit(local_variable_index()); - } else { - generator.emit(generator.intern_identifier(m_string), generator.next_environment_variable_cache()); + + if (is_local()) { + auto local = Bytecode::Operand(Bytecode::Operand::Type::Local, local_variable_index()); + if (!generator.is_local_initialized(local_variable_index())) { + generator.emit(local); + } + return local; } - return Optional {}; + + auto dst = choose_dst(generator, preferred_dst); + if (is_global()) { + generator.emit(dst, generator.intern_identifier(m_string), generator.next_global_variable_cache()); + } else { + generator.emit(dst, generator.intern_identifier(m_string), generator.next_environment_variable_cache()); + } + return dst; } -static Bytecode::CodeGenerationErrorOr> arguments_to_array_for_call(Bytecode::Generator& generator, ReadonlySpan arguments, [[maybe_unused]] Optional preferred_dst = {}) +static Bytecode::CodeGenerationErrorOr> arguments_to_array_for_call(Bytecode::Generator& generator, ReadonlySpan arguments) { - + auto dst = Bytecode::Operand(generator.allocate_register()); if (arguments.is_empty()) { - generator.emit(); - return Optional {}; + generator.emit(dst); + return dst; } auto first_spread = find_if(arguments.begin(), arguments.end(), [](auto el) { return el.is_spread; }); @@ -341,51 +363,49 @@ static Bytecode::CodeGenerationErrorOr> arguments_to for (auto it = arguments.begin(); it != first_spread; ++it, ++i) { VERIFY(it->is_spread == false); Bytecode::Register reg { args_start_reg.index() + i }; - FIXME_NEWBC TRY(it->value->generate_bytecode(generator)); - generator.emit(reg); + auto value = TRY(it->value->generate_bytecode(generator)).value(); + generator.emit(Bytecode::Operand(reg), value); } if (first_spread.index() != 0) - generator.emit_with_extra_register_slots(2u, AK::Array { args_start_reg, Bytecode::Register { args_start_reg.index() + static_cast(first_spread.index() - 1) } }); + generator.emit_with_extra_operand_slots(2u, dst, AK::Array { Bytecode::Operand(args_start_reg), Bytecode::Operand { Bytecode::Register { args_start_reg.index() + static_cast(first_spread.index() - 1) } } }); else - generator.emit(); + generator.emit(dst); if (first_spread != arguments.end()) { - auto array_reg = generator.allocate_register(); - generator.emit(array_reg); for (auto it = first_spread; it != arguments.end(); ++it) { - FIXME_NEWBC TRY(it->value->generate_bytecode(generator)); - generator.emit(array_reg, it->is_spread); + auto value = TRY(it->value->generate_bytecode(generator)).value(); + generator.emit(dst, value, it->is_spread); } - generator.emit(array_reg); } - return Optional {}; + return dst; } -Bytecode::CodeGenerationErrorOr> SuperCall::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> SuperCall::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); + Optional arguments; if (m_is_synthetic == IsPartOfSyntheticConstructor::Yes) { // NOTE: This is the case where we have a fake constructor(...args) { super(...args); } which // shouldn't call @@iterator of %Array.prototype%. VERIFY(m_arguments.size() == 1); VERIFY(m_arguments[0].is_spread); auto const& argument = m_arguments[0]; - // This generates a single argument, which will be implicitly passed in accumulator - FIXME_NEWBC MUST(argument.value->generate_bytecode(generator)); + // This generates a single argument. + arguments = MUST(argument.value->generate_bytecode(generator)); } else { - FIXME_NEWBC TRY(arguments_to_array_for_call(generator, m_arguments)); + arguments = TRY(arguments_to_array_for_call(generator, m_arguments)).value(); } - generator.emit(m_is_synthetic == IsPartOfSyntheticConstructor::Yes); - - return Optional {}; + auto dst = choose_dst(generator, preferred_dst); + generator.emit(dst, *arguments, m_is_synthetic == IsPartOfSyntheticConstructor::Yes); + return dst; } -static Bytecode::CodeGenerationErrorOr> generate_binding_pattern_bytecode(Bytecode::Generator& generator, BindingPattern const& pattern, Bytecode::Op::SetVariable::InitializationMode, Bytecode::Register const& value_reg, bool create_variables, [[maybe_unused]] Optional preferred_dst = {}); +static Bytecode::CodeGenerationErrorOr generate_binding_pattern_bytecode(Bytecode::Generator& generator, BindingPattern const& pattern, Bytecode::Op::SetVariable::InitializationMode, Bytecode::Operand const& input_value, bool create_variables); -Bytecode::CodeGenerationErrorOr> AssignmentExpression::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> AssignmentExpression::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); if (m_op == AssignmentOp::Assignment) { @@ -395,27 +415,24 @@ Bytecode::CodeGenerationErrorOr> AssignmentExpressio [&](NonnullRefPtr const& lhs) -> Bytecode::CodeGenerationErrorOr> { // a. Let lref be the result of evaluating LeftHandSideExpression. // b. ReturnIfAbrupt(lref). - Optional base_object_register; - Optional computed_property_register; - Optional this_value_register; + Optional base; + Optional computed_property; + Optional this_value; bool lhs_is_super_expression = false; if (is(*lhs)) { auto& expression = static_cast(*lhs); lhs_is_super_expression = is(expression.object()); - base_object_register = generator.allocate_register(); if (!lhs_is_super_expression) { - FIXME_NEWBC TRY(expression.object().generate_bytecode(generator)); - generator.emit(*base_object_register); + base = TRY(expression.object().generate_bytecode(generator)).value(); } 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); + this_value = Bytecode::Operand(generator.allocate_register()); + generator.emit(*this_value); // SuperProperty : super [ Expression ] // 3. Let propertyNameReference be ? Evaluation of Expression. @@ -423,9 +440,7 @@ Bytecode::CodeGenerationErrorOr> AssignmentExpressio } if (expression.is_computed()) { - FIXME_NEWBC TRY(expression.property().generate_bytecode(generator)); - computed_property_register = generator.allocate_register(); - generator.emit(*computed_property_register); + computed_property = TRY(expression.property().generate_bytecode(generator)).value(); // To be continued later with PutByValue. } else if (expression.property().is_identifier()) { @@ -447,14 +462,14 @@ Bytecode::CodeGenerationErrorOr> AssignmentExpressio // 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); + base = Bytecode::Operand(generator.allocate_register()); + generator.emit(*base); } } 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. } else { - FIXME_NEWBC TRY(lhs->generate_bytecode(generator)); + (void)TRY(lhs->generate_bytecode(generator)); } // FIXME: c. If IsAnonymousFunctionDefinition(AssignmentExpression) and IsIdentifierRef of LeftHandSideExpression are both true, then @@ -463,33 +478,35 @@ Bytecode::CodeGenerationErrorOr> AssignmentExpressio // d. Else, // i. Let rref be the result of evaluating AssignmentExpression. // ii. Let rval be ? GetValue(rref). - if (lhs->is_identifier()) { - TRY(generator.emit_named_evaluation_if_anonymous_function(*m_rhs, generator.intern_identifier(static_cast(*lhs).string()))); - } else { - FIXME_NEWBC TRY(m_rhs->generate_bytecode(generator)); - } + auto rval = TRY([&]() -> Bytecode::CodeGenerationErrorOr { + if (lhs->is_identifier()) { + return TRY(generator.emit_named_evaluation_if_anonymous_function(*m_rhs, generator.intern_identifier(static_cast(*lhs).string()))).value(); + } else { + return TRY(m_rhs->generate_bytecode(generator)).value(); + } + }()); // e. Perform ? PutValue(lref, rval). if (is(*lhs)) { auto& identifier = static_cast(*lhs); - generator.emit_set_variable(identifier); + generator.emit_set_variable(identifier, rval); } else if (is(*lhs)) { auto& expression = static_cast(*lhs); if (expression.is_computed()) { if (!lhs_is_super_expression) - generator.emit(*base_object_register, *computed_property_register); + generator.emit(*base, *computed_property, rval); else - generator.emit(*base_object_register, *computed_property_register, *this_value_register); + generator.emit(*base, *computed_property, *this_value, rval); } else if (expression.property().is_identifier()) { auto identifier_table_ref = generator.intern_identifier(verify_cast(expression.property()).string()); if (!lhs_is_super_expression) - generator.emit(*base_object_register, identifier_table_ref, Bytecode::Op::PropertyKind::KeyValue, generator.next_property_lookup_cache()); + generator.emit(*base, identifier_table_ref, rval, Bytecode::Op::PropertyKind::KeyValue, generator.next_property_lookup_cache()); else - generator.emit(*base_object_register, *this_value_register, identifier_table_ref, Bytecode::Op::PropertyKind::KeyValue, generator.next_property_lookup_cache()); + generator.emit(*base, *this_value, identifier_table_ref, rval, Bytecode::Op::PropertyKind::KeyValue, generator.next_property_lookup_cache()); } 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); + generator.emit(*base, identifier_table_ref, rval); } else { return Bytecode::CodeGenerationError { &expression, @@ -504,112 +521,116 @@ Bytecode::CodeGenerationErrorOr> AssignmentExpressio } // f. Return rval. - // NOTE: This is already in the accumulator. - return Optional {}; + return rval; }, // 2. Let assignmentPattern be the AssignmentPattern that is covered by LeftHandSideExpression. [&](NonnullRefPtr const& pattern) -> Bytecode::CodeGenerationErrorOr> { // 3. Let rref be the result of evaluating AssignmentExpression. // 4. Let rval be ? GetValue(rref). - FIXME_NEWBC TRY(m_rhs->generate_bytecode(generator)); - auto value_register = generator.allocate_register(); - generator.emit(value_register); + auto rval = TRY(m_rhs->generate_bytecode(generator)).value(); // 5. Perform ? DestructuringAssignmentEvaluation of assignmentPattern with argument rval. - FIXME_NEWBC TRY(generate_binding_pattern_bytecode(generator, pattern, Bytecode::Op::SetVariable::InitializationMode::Set, value_register, false)); + TRY(generate_binding_pattern_bytecode(generator, pattern, Bytecode::Op::SetVariable::InitializationMode::Set, rval, false)); // 6. Return rval. - generator.emit(value_register); - return Optional {}; + return rval; }); } VERIFY(m_lhs.has>()); - auto& lhs = m_lhs.get>(); + auto& lhs_expression = m_lhs.get>(); - auto reference_registers = TRY(generator.emit_load_from_reference(lhs, Bytecode::Generator::CollectRegisters::Yes)); + auto reference_operands = TRY(generator.emit_load_from_reference(lhs_expression)); + auto lhs = reference_operands.loaded_value.value(); Bytecode::BasicBlock* rhs_block_ptr { nullptr }; + Bytecode::BasicBlock* lhs_block_ptr { nullptr }; Bytecode::BasicBlock* end_block_ptr { nullptr }; // Logical assignments short circuit. if (m_op == AssignmentOp::AndAssignment) { // &&= rhs_block_ptr = &generator.make_block(); + lhs_block_ptr = &generator.make_block(); end_block_ptr = &generator.make_block(); - generator.emit( + generator.emit( + lhs, Bytecode::Label { *rhs_block_ptr }, - Bytecode::Label { *end_block_ptr }); + Bytecode::Label { *lhs_block_ptr }); } else if (m_op == AssignmentOp::OrAssignment) { // ||= rhs_block_ptr = &generator.make_block(); + lhs_block_ptr = &generator.make_block(); end_block_ptr = &generator.make_block(); - generator.emit( - Bytecode::Label { *end_block_ptr }, + generator.emit( + lhs, + Bytecode::Label { *lhs_block_ptr }, Bytecode::Label { *rhs_block_ptr }); } else if (m_op == AssignmentOp::NullishAssignment) { // ??= rhs_block_ptr = &generator.make_block(); + lhs_block_ptr = &generator.make_block(); end_block_ptr = &generator.make_block(); generator.emit( + lhs, Bytecode::Label { *rhs_block_ptr }, - Bytecode::Label { *end_block_ptr }); + Bytecode::Label { *lhs_block_ptr }); } if (rhs_block_ptr) generator.switch_to_basic_block(*rhs_block_ptr); - // lhs_reg is a part of the rhs_block because the store isn't necessary - // if the logical assignment condition fails. - auto lhs_reg = generator.allocate_register(); - generator.emit(lhs_reg); + auto rhs = TRY([&]() -> Bytecode::CodeGenerationErrorOr { + if (lhs_expression->is_identifier()) { + return TRY(generator.emit_named_evaluation_if_anonymous_function(*m_rhs, generator.intern_identifier(static_cast(*lhs_expression).string()))).value(); + } + return TRY(m_rhs->generate_bytecode(generator)).value(); + }()); - if (lhs->is_identifier()) - TRY(generator.emit_named_evaluation_if_anonymous_function(*m_rhs, generator.intern_identifier(static_cast(*lhs).string()))); - else - FIXME_NEWBC TRY(m_rhs->generate_bytecode(generator)); + auto dst = choose_dst(generator, preferred_dst); switch (m_op) { case AssignmentOp::AdditionAssignment: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case AssignmentOp::SubtractionAssignment: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case AssignmentOp::MultiplicationAssignment: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case AssignmentOp::DivisionAssignment: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case AssignmentOp::ModuloAssignment: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case AssignmentOp::ExponentiationAssignment: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case AssignmentOp::BitwiseAndAssignment: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case AssignmentOp::BitwiseOrAssignment: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case AssignmentOp::BitwiseXorAssignment: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case AssignmentOp::LeftShiftAssignment: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case AssignmentOp::RightShiftAssignment: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case AssignmentOp::UnsignedRightShiftAssignment: - generator.emit(lhs_reg); + generator.emit(dst, lhs, rhs); break; case AssignmentOp::AndAssignment: case AssignmentOp::OrAssignment: case AssignmentOp::NullishAssignment: - break; // These are handled above. + generator.emit(dst, rhs); + break; default: return Bytecode::CodeGenerationError { this, @@ -617,17 +638,26 @@ Bytecode::CodeGenerationErrorOr> AssignmentExpressio }; } - if (reference_registers.has_value()) - FIXME_NEWBC TRY(generator.emit_store_to_reference(*reference_registers)); + if (lhs_expression->is_identifier()) + generator.emit_set_variable(static_cast(*lhs_expression), dst); else - FIXME_NEWBC TRY(generator.emit_store_to_reference(lhs)); + (void)TRY(generator.emit_store_to_reference(reference_operands, dst)); + + if (rhs_block_ptr) { + generator.emit(Bytecode::Label { *end_block_ptr }); + } + + if (lhs_block_ptr) { + generator.switch_to_basic_block(*lhs_block_ptr); + generator.emit(dst, lhs); + generator.emit(Bytecode::Label { *end_block_ptr }); + } if (end_block_ptr) { - generator.emit(Bytecode::Label { *end_block_ptr }); generator.switch_to_basic_block(*end_block_ptr); } - return Optional {}; + return dst; } // 14.13.3 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-labelled-statements-runtime-semantics-evaluation @@ -656,22 +686,22 @@ Bytecode::CodeGenerationErrorOr> LabelledStatement:: new_label_set.append(m_label); // 3. Let stmtResult be LabelledEvaluation of LabelledItem with argument newLabelSet. - // NOTE: stmtResult will be in the accumulator after running the generated bytecode. + Optional stmt_result; if (is(labelled_item)) { auto const& iteration_statement = static_cast(labelled_item); - FIXME_NEWBC TRY(iteration_statement.generate_labelled_evaluation(generator, new_label_set)); + stmt_result = TRY(iteration_statement.generate_labelled_evaluation(generator, new_label_set)); } else if (is(labelled_item)) { auto const& switch_statement = static_cast(labelled_item); - FIXME_NEWBC TRY(switch_statement.generate_labelled_evaluation(generator, new_label_set)); + stmt_result = TRY(switch_statement.generate_labelled_evaluation(generator, new_label_set)); } else if (is(labelled_item)) { auto const& labelled_statement = static_cast(labelled_item); - FIXME_NEWBC TRY(labelled_statement.generate_labelled_evaluation(generator, new_label_set)); + stmt_result = TRY(labelled_statement.generate_labelled_evaluation(generator, new_label_set)); } else { auto& labelled_break_block = generator.make_block(); // NOTE: We do not need a continuable scope as `continue;` is not allowed outside of iteration statements, throwing a SyntaxError in the parser. generator.begin_breakable_scope(Bytecode::Label { labelled_break_block }, new_label_set); - FIXME_NEWBC TRY(labelled_item.generate_bytecode(generator)); + stmt_result = TRY(labelled_item.generate_bytecode(generator)); generator.end_breakable_scope(); if (!generator.is_current_block_terminated()) { @@ -686,8 +716,7 @@ Bytecode::CodeGenerationErrorOr> LabelledStatement:: // NOTE: These steps are performed by making labelled break jump straight to the appropriate break block, which preserves the statement result's value in the accumulator. // 5. Return Completion(stmtResult). - // NOTE: This is in the accumulator. - return Optional {}; + return stmt_result; } Bytecode::CodeGenerationErrorOr> IterationStatement::generate_labelled_evaluation(Bytecode::Generator&, Vector const&, [[maybe_unused]] Optional preferred_dst) const @@ -714,41 +743,35 @@ Bytecode::CodeGenerationErrorOr> WhileStatement::gen // end auto& test_block = generator.make_block(); auto& body_block = generator.make_block(); - auto& load_result_and_jump_to_end_block = generator.make_block(); auto& end_block = generator.make_block(); - // Init result register - generator.emit(js_undefined()); - auto result_reg = generator.allocate_register(); + auto result = Bytecode::Operand(generator.allocate_register()); + generator.emit(result, generator.add_constant(js_undefined())); - // jump to the test block generator.emit(Bytecode::Label { test_block }); generator.switch_to_basic_block(test_block); - generator.emit(result_reg); - FIXME_NEWBC TRY(m_test->generate_bytecode(generator)); - generator.emit( + auto test = TRY(m_test->generate_bytecode(generator)).value(); + generator.emit( + test, Bytecode::Label { body_block }, - Bytecode::Label { load_result_and_jump_to_end_block }); + Bytecode::Label { end_block }); generator.switch_to_basic_block(body_block); generator.begin_continuable_scope(Bytecode::Label { test_block }, label_set); generator.begin_breakable_scope(Bytecode::Label { end_block }, label_set); - generator.emit(js_undefined()); - FIXME_NEWBC TRY(m_body->generate_bytecode(generator)); + auto body = TRY(m_body->generate_bytecode(generator)); generator.end_breakable_scope(); generator.end_continuable_scope(); if (!generator.is_current_block_terminated()) { + if (body.has_value()) + generator.emit(result, body.value()); generator.emit(Bytecode::Label { test_block }); } - generator.switch_to_basic_block(load_result_and_jump_to_end_block); - generator.emit(result_reg); - generator.emit(Bytecode::Label { end_block }); - generator.switch_to_basic_block(end_block); - return Optional {}; + return result; } Bytecode::CodeGenerationErrorOr> DoWhileStatement::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const @@ -771,38 +794,37 @@ Bytecode::CodeGenerationErrorOr> DoWhileStatement::g auto& load_result_and_jump_to_end_block = generator.make_block(); auto& end_block = generator.make_block(); - // Init result register - generator.emit(js_undefined()); - auto result_reg = generator.allocate_register(); - generator.emit(result_reg); + auto completion_value = Bytecode::Operand(generator.allocate_register()); + generator.emit(completion_value, generator.add_constant(js_undefined())); // jump to the body block generator.emit(Bytecode::Label { body_block }); generator.switch_to_basic_block(test_block); - generator.emit(result_reg); - FIXME_NEWBC TRY(m_test->generate_bytecode(generator)); - generator.emit( + auto test = TRY(m_test->generate_bytecode(generator)).value(); + generator.emit( + test, Bytecode::Label { body_block }, Bytecode::Label { load_result_and_jump_to_end_block }); generator.switch_to_basic_block(body_block); generator.begin_continuable_scope(Bytecode::Label { test_block }, label_set); generator.begin_breakable_scope(Bytecode::Label { end_block }, label_set); - FIXME_NEWBC TRY(m_body->generate_bytecode(generator)); + auto body_result = TRY(m_body->generate_bytecode(generator)); generator.end_breakable_scope(); generator.end_continuable_scope(); if (!generator.is_current_block_terminated()) { + if (body_result.has_value()) + generator.emit(completion_value, body_result.value()); generator.emit(Bytecode::Label { test_block }); } generator.switch_to_basic_block(load_result_and_jump_to_end_block); - generator.emit(result_reg); generator.emit(Bytecode::Label { end_block }); generator.switch_to_basic_block(end_block); - return Optional {}; + return completion_value; } Bytecode::CodeGenerationErrorOr> ForStatement::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const @@ -863,7 +885,7 @@ Bytecode::CodeGenerationErrorOr> ForStatement::gener } } - FIXME_NEWBC TRY(m_init->generate_bytecode(generator)); + (void)TRY(m_init->generate_bytecode(generator)); } body_block_ptr = &generator.make_block(); @@ -878,23 +900,15 @@ Bytecode::CodeGenerationErrorOr> ForStatement::gener else update_block_ptr = body_block_ptr; - generator.emit(js_undefined()); - auto result_reg = generator.allocate_register(); - - if (m_test && m_update) - generator.emit(result_reg); - generator.emit(Bytecode::Label { *test_block_ptr }); if (m_test) { load_result_and_jump_to_end_block_ptr = &generator.make_block(); generator.switch_to_basic_block(*test_block_ptr); - if (!m_update) - generator.emit(result_reg); - - FIXME_NEWBC TRY(m_test->generate_bytecode(generator)); - generator.emit( + auto test = TRY(m_test->generate_bytecode(generator)).value(); + generator.emit( + test, Bytecode::Label { *body_block_ptr }, Bytecode::Label { *load_result_and_jump_to_end_block_ptr }); } @@ -902,18 +916,14 @@ Bytecode::CodeGenerationErrorOr> ForStatement::gener if (m_update) { generator.switch_to_basic_block(*update_block_ptr); - if (m_test) - generator.emit(result_reg); - - FIXME_NEWBC TRY(m_update->generate_bytecode(generator)); + (void)TRY(m_update->generate_bytecode(generator)); generator.emit(Bytecode::Label { *test_block_ptr }); } generator.switch_to_basic_block(*body_block_ptr); generator.begin_continuable_scope(Bytecode::Label { m_update ? *update_block_ptr : *test_block_ptr }, label_set); generator.begin_breakable_scope(Bytecode::Label { end_block }, label_set); - generator.emit(js_undefined()); - FIXME_NEWBC TRY(m_body->generate_bytecode(generator)); + auto body_result = TRY(m_body->generate_bytecode(generator)); generator.end_breakable_scope(); generator.end_continuable_scope(); @@ -927,7 +937,6 @@ Bytecode::CodeGenerationErrorOr> ForStatement::gener if (load_result_and_jump_to_end_block_ptr) { generator.switch_to_basic_block(*load_result_and_jump_to_end_block_ptr); - generator.emit(result_reg); generator.emit(Bytecode::Label { end_block }); } @@ -936,20 +945,20 @@ Bytecode::CodeGenerationErrorOr> ForStatement::gener if (has_lexical_environment) generator.end_variable_scope(); - return Optional {}; + return body_result; } -Bytecode::CodeGenerationErrorOr> ObjectExpression::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> ObjectExpression::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - generator.emit(); + + auto object = choose_dst(generator, preferred_dst); + + generator.emit(object); if (m_properties.is_empty()) - return Optional {}; + return object; - auto object_reg = generator.allocate_register(); - generator.emit(object_reg); - - generator.push_home_object(object_reg); + generator.push_home_object(object); for (auto& property : m_properties) { Bytecode::Op::PropertyKind property_kind; @@ -975,8 +984,9 @@ Bytecode::CodeGenerationErrorOr> ObjectExpression::g auto& string_literal = static_cast(property->key()); Bytecode::IdentifierTableIndex key_name = generator.intern_identifier(string_literal.value()); + Optional value; if (property_kind == Bytecode::Op::PropertyKind::ProtoSetter) { - FIXME_NEWBC TRY(property->value().generate_bytecode(generator)); + value = TRY(property->value().generate_bytecode(generator)).value(); } else if (property_kind != Bytecode::Op::PropertyKind::Spread) { ByteString identifier = string_literal.value(); if (property_kind == Bytecode::Op::PropertyKind::Getter) @@ -984,34 +994,36 @@ Bytecode::CodeGenerationErrorOr> ObjectExpression::g else if (property_kind == Bytecode::Op::PropertyKind::Setter) identifier = ByteString::formatted("set {}", identifier); auto name = generator.intern_identifier(identifier); - TRY(generator.emit_named_evaluation_if_anonymous_function(property->value(), name)); + value = TRY(generator.emit_named_evaluation_if_anonymous_function(property->value(), name)).value(); + } else { + // Spread the key. + value = TRY(property->key().generate_bytecode(generator)).value(); } - generator.emit(object_reg, key_name, property_kind, generator.next_property_lookup_cache()); + generator.emit(object, key_name, *value, property_kind, generator.next_property_lookup_cache()); } else { - FIXME_NEWBC TRY(property->key().generate_bytecode(generator)); - auto property_reg = generator.allocate_register(); - generator.emit(property_reg); - + auto property_name = TRY(property->key().generate_bytecode(generator)).value(); + Optional value; if (property_kind != Bytecode::Op::PropertyKind::Spread) - FIXME_NEWBC TRY(property->value().generate_bytecode(generator)); + value = TRY(property->value().generate_bytecode(generator)).value(); + else + value = property_name; - generator.emit(object_reg, property_reg, property_kind); + generator.emit(object, property_name, *value, property_kind); } } - generator.emit(object_reg); - generator.pop_home_object(); - return Optional {}; + return object; } -Bytecode::CodeGenerationErrorOr> ArrayExpression::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> ArrayExpression::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); if (m_elements.is_empty()) { - generator.emit(); - return Optional {}; + auto dst = choose_dst(generator, preferred_dst); + generator.emit(dst); + return dst; } if (all_of(m_elements, [](auto element) { return !element || is(*element); })) { @@ -1023,8 +1035,9 @@ Bytecode::CodeGenerationErrorOr> ArrayExpression::ge continue; values[i] = static_cast(*m_elements[i]).value(); } - generator.emit(move(values)); - return Optional {}; + auto dst = choose_dst(generator, preferred_dst); + generator.emit(dst, move(values)); + return dst; } auto first_spread = find_if(m_elements.begin(), m_elements.end(), [](auto el) { return el && is(*el); }); @@ -1038,42 +1051,39 @@ Bytecode::CodeGenerationErrorOr> ArrayExpression::ge u32 i = 0; for (auto it = m_elements.begin(); it != first_spread; ++it, ++i) { Bytecode::Register reg { args_start_reg.index() + i }; - if (!*it) - generator.emit(Value {}); - else { - FIXME_NEWBC TRY((*it)->generate_bytecode(generator)); + if (*it) { + auto value = TRY((*it)->generate_bytecode(generator)).value(); + generator.emit(Bytecode::Operand(reg), value); } - generator.emit(reg); } - if (first_spread.index() != 0) - generator.emit_with_extra_register_slots(2u, AK::Array { args_start_reg, Bytecode::Register { args_start_reg.index() + static_cast(first_spread.index() - 1) } }); - else - generator.emit(); + auto dst = choose_dst(generator, preferred_dst); + if (first_spread.index() != 0) { + auto reg = Bytecode::Register { args_start_reg.index() + static_cast(first_spread.index() - 1) }; + generator.emit_with_extra_operand_slots(2u, dst, AK::Array { Bytecode::Operand(args_start_reg), Bytecode::Operand(reg) }); + } else { + generator.emit(dst); + } if (first_spread != m_elements.end()) { - auto array_reg = generator.allocate_register(); - generator.emit(array_reg); for (auto it = first_spread; it != m_elements.end(); ++it) { if (!*it) { - generator.emit(Value {}); - generator.emit(array_reg, false); + generator.emit(dst, generator.add_constant(Value()), false); } else { - FIXME_NEWBC TRY((*it)->generate_bytecode(generator)); - generator.emit(array_reg, *it && is(**it)); + auto value = TRY((*it)->generate_bytecode(generator)).value(); + generator.emit(dst, value, *it && is(**it)); } } - generator.emit(array_reg); } - return Optional {}; + return dst; } -Bytecode::CodeGenerationErrorOr> MemberExpression::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> MemberExpression::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - (void)TRY(generator.emit_load_from_reference(*this, Bytecode::Generator::CollectRegisters::No)); - return Optional {}; + auto reference = TRY(generator.emit_load_from_reference(*this, preferred_dst)); + return reference.loaded_value; } Bytecode::CodeGenerationErrorOr> FunctionDeclaration::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const @@ -1081,13 +1091,14 @@ Bytecode::CodeGenerationErrorOr> FunctionDeclaration if (m_is_hoisted) { Bytecode::Generator::SourceLocationScope scope(generator, *this); auto index = generator.intern_identifier(name()); - generator.emit(index, generator.next_environment_variable_cache()); - generator.emit(index, generator.next_environment_variable_cache(), Bytecode::Op::SetVariable::InitializationMode::Set, Bytecode::Op::EnvironmentMode::Var); + auto value = Bytecode::Operand(generator.allocate_register()); + generator.emit(value, index, generator.next_environment_variable_cache()); + generator.emit(index, value, generator.next_environment_variable_cache(), Bytecode::Op::SetVariable::InitializationMode::Set, Bytecode::Op::EnvironmentMode::Var); } return Optional {}; } -Bytecode::CodeGenerationErrorOr> FunctionExpression::generate_bytecode_with_lhs_name(Bytecode::Generator& generator, Optional lhs_name, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> FunctionExpression::generate_bytecode_with_lhs_name(Bytecode::Generator& generator, Optional lhs_name, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); bool has_name = !name().is_empty(); @@ -1100,27 +1111,28 @@ Bytecode::CodeGenerationErrorOr> FunctionExpression: generator.emit(*name_identifier, Bytecode::Op::EnvironmentMode::Lexical, true); } - generator.emit_new_function(*this, lhs_name); + auto new_function = choose_dst(generator, preferred_dst); + generator.emit_new_function(new_function, *this, lhs_name); if (has_name) { - generator.emit(*name_identifier, generator.next_environment_variable_cache(), Bytecode::Op::SetVariable::InitializationMode::Initialize, Bytecode::Op::EnvironmentMode::Lexical); + generator.emit(*name_identifier, new_function, generator.next_environment_variable_cache(), Bytecode::Op::SetVariable::InitializationMode::Initialize, Bytecode::Op::EnvironmentMode::Lexical); generator.end_variable_scope(); } - return Optional {}; + return new_function; } -Bytecode::CodeGenerationErrorOr> FunctionExpression::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> FunctionExpression::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - return generate_bytecode_with_lhs_name(generator, {}); + return generate_bytecode_with_lhs_name(generator, {}, preferred_dst); } -static Bytecode::CodeGenerationErrorOr> generate_object_binding_pattern_bytecode(Bytecode::Generator& generator, BindingPattern const& pattern, Bytecode::Op::SetVariable::InitializationMode initialization_mode, Bytecode::Register const& value_reg, bool create_variables, [[maybe_unused]] Optional preferred_dst = {}) +static Bytecode::CodeGenerationErrorOr generate_object_binding_pattern_bytecode(Bytecode::Generator& generator, BindingPattern const& pattern, Bytecode::Op::SetVariable::InitializationMode initialization_mode, Bytecode::Operand const& object, bool create_variables) { - generator.emit(); + generator.emit(object); - Vector excluded_property_names; + Vector excluded_property_names; auto has_rest = false; if (pattern.entries.size() > 0) has_rest = pattern.entries[pattern.entries.size() - 1].is_rest; @@ -1132,49 +1144,53 @@ static Bytecode::CodeGenerationErrorOr> generate_obj auto identifier = name.get>(); auto interned_identifier = generator.intern_identifier(identifier->string()); - generator.emit_with_extra_register_slots(excluded_property_names.size(), value_reg, excluded_property_names); + auto copy = Bytecode::Operand(generator.allocate_register()); + generator.emit_with_extra_operand_slots( + excluded_property_names.size(), copy, object, excluded_property_names); if (create_variables) { VERIFY(!identifier->is_local()); generator.emit(interned_identifier, Bytecode::Op::EnvironmentMode::Lexical, false); } - generator.emit_set_variable(*identifier, initialization_mode); + generator.emit_set_variable(*identifier, copy, initialization_mode); - return Optional {}; + return {}; } if (alias.has>()) { - generator.emit_with_extra_register_slots(excluded_property_names.size(), value_reg, excluded_property_names); - FIXME_NEWBC TRY(generator.emit_store_to_reference(alias.get>())); - return Optional {}; + auto copy = Bytecode::Operand(generator.allocate_register()); + generator.emit_with_extra_operand_slots( + excluded_property_names.size(), copy, object, excluded_property_names); + (void)TRY(generator.emit_store_to_reference(alias.get>(), object)); + return {}; } VERIFY_NOT_REACHED(); } Bytecode::StringTableIndex name_index; + auto value = Bytecode::Operand(generator.allocate_register()); + if (name.has>()) { auto identifier = name.get>()->string(); name_index = generator.intern_string(identifier); if (has_rest) { - auto excluded_name_reg = generator.allocate_register(); - excluded_property_names.append(excluded_name_reg); - generator.emit(name_index); - generator.emit(excluded_name_reg); + auto excluded_name = Bytecode::Operand(generator.allocate_register()); + excluded_property_names.append(excluded_name); + generator.emit(excluded_name, name_index); } - generator.emit(value_reg); - generator.emit_get_by_id(generator.intern_identifier(identifier)); + generator.emit_get_by_id(value, object, generator.intern_identifier(identifier)); } else { auto expression = name.get>(); - FIXME_NEWBC TRY(expression->generate_bytecode(generator)); + auto property_name = TRY(expression->generate_bytecode(generator)).value(); if (has_rest) { - auto excluded_name_reg = generator.allocate_register(); - excluded_property_names.append(excluded_name_reg); - generator.emit(excluded_name_reg); + auto excluded_name = Bytecode::Operand(generator.allocate_register()); + excluded_property_names.append(excluded_name); + generator.emit(excluded_name, property_name); } - generator.emit(value_reg); + generator.emit(value, object, property_name); } if (initializer) { @@ -1182,17 +1198,20 @@ static Bytecode::CodeGenerationErrorOr> generate_obj auto& if_not_undefined_block = generator.make_block(); generator.emit( + value, Bytecode::Label { if_undefined_block }, Bytecode::Label { if_not_undefined_block }); generator.switch_to_basic_block(if_undefined_block); + Optional default_value; if (auto const* alias_identifier = alias.get_pointer>()) { - TRY(generator.emit_named_evaluation_if_anonymous_function(*initializer, generator.intern_identifier((*alias_identifier)->string()))); + default_value = TRY(generator.emit_named_evaluation_if_anonymous_function(*initializer, generator.intern_identifier((*alias_identifier)->string()))).value(); } else if (auto const* lhs = name.get_pointer>()) { - TRY(generator.emit_named_evaluation_if_anonymous_function(*initializer, generator.intern_identifier((*lhs)->string()))); + default_value = TRY(generator.emit_named_evaluation_if_anonymous_function(*initializer, generator.intern_identifier((*lhs)->string()))).value(); } else { - FIXME_NEWBC TRY(initializer->generate_bytecode(generator)); + default_value = TRY(initializer->generate_bytecode(generator)).value(); } + generator.emit(value, *default_value); generator.emit(Bytecode::Label { if_not_undefined_block }); generator.switch_to_basic_block(if_not_undefined_block); @@ -1200,9 +1219,9 @@ static Bytecode::CodeGenerationErrorOr> generate_obj if (alias.has>()) { auto& binding_pattern = *alias.get>(); - auto nested_value_reg = generator.allocate_register(); - generator.emit(nested_value_reg); - FIXME_NEWBC TRY(generate_binding_pattern_bytecode(generator, binding_pattern, initialization_mode, nested_value_reg, create_variables)); + auto nested_value = Bytecode::Operand(generator.allocate_register()); + generator.emit(nested_value, value); + TRY(generate_binding_pattern_bytecode(generator, binding_pattern, initialization_mode, nested_value, create_variables)); } else if (alias.has()) { if (name.has>()) { // This needs some sort of SetVariableByValue opcode, as it's a runtime binding @@ -1216,21 +1235,21 @@ static Bytecode::CodeGenerationErrorOr> generate_obj auto identifier_ref = generator.intern_identifier(identifier.string()); if (create_variables) generator.emit(identifier_ref, Bytecode::Op::EnvironmentMode::Lexical, false); - generator.emit_set_variable(identifier, initialization_mode); + generator.emit_set_variable(identifier, value, initialization_mode); } else if (alias.has>()) { - FIXME_NEWBC TRY(generator.emit_store_to_reference(alias.get>())); + TRY(generator.emit_store_to_reference(alias.get>(), value)); } else { auto const& identifier = *alias.get>(); auto identifier_ref = generator.intern_identifier(identifier.string()); if (create_variables) generator.emit(identifier_ref, Bytecode::Op::EnvironmentMode::Lexical, false); - generator.emit_set_variable(identifier, initialization_mode); + generator.emit_set_variable(identifier, value, initialization_mode); } } - return Optional {}; + return {}; } -static Bytecode::CodeGenerationErrorOr> generate_array_binding_pattern_bytecode(Bytecode::Generator& generator, BindingPattern const& pattern, Bytecode::Op::SetVariable::InitializationMode initialization_mode, Bytecode::Register const& value_reg, bool create_variables, [[maybe_unused]] Optional preferred_dst = {}) +static Bytecode::CodeGenerationErrorOr generate_array_binding_pattern_bytecode(Bytecode::Generator& generator, BindingPattern const& pattern, Bytecode::Op::SetVariable::InitializationMode initialization_mode, Bytecode::Operand const& input_array, bool create_variables, [[maybe_unused]] Optional preferred_dst = {}) { /* * Consider the following destructuring assignment: @@ -1245,134 +1264,119 @@ static Bytecode::CodeGenerationErrorOr> generate_arr * - Reserve a special boolean register which holds 'true' if the iterator is exhausted, * and false otherwise * - When we are retrieving the value which should be bound, we first check this register. - * If it is 'true', we load undefined into the accumulator. Otherwise, we grab the next - * value from the iterator and store it into the accumulator. + * If it is 'true', we load undefined. Otherwise, we grab the next value from the iterator. * * Note that the is_exhausted register does not need to be loaded with false because the * first IteratorNext bytecode is _not_ proceeded by an exhausted check, as it is * unnecessary. */ - auto is_iterator_exhausted_register = generator.allocate_register(); - generator.emit(Value(false)); - generator.emit(is_iterator_exhausted_register); + auto is_iterator_exhausted = Bytecode::Operand(generator.allocate_register()); + generator.emit(is_iterator_exhausted, generator.add_constant(Value(false))); - auto iterator_reg = generator.allocate_register(); - generator.emit(value_reg); - generator.emit(); - generator.emit(iterator_reg); + auto iterator = Bytecode::Operand(generator.allocate_register()); + generator.emit(iterator, input_array); bool first = true; - auto temp_iterator_result_reg = generator.allocate_register(); - - auto assign_accumulator_to_alias = [&](auto& alias) { + auto assign_value_to_alias = [&](auto& alias, Bytecode::Operand value) { return alias.visit( - [&](Empty) -> Bytecode::CodeGenerationErrorOr> { + [&](Empty) -> Bytecode::CodeGenerationErrorOr { // This element is an elision - return Optional {}; + return {}; }, - [&](NonnullRefPtr const& identifier) -> Bytecode::CodeGenerationErrorOr> { + [&](NonnullRefPtr const& identifier) -> Bytecode::CodeGenerationErrorOr { auto interned_index = generator.intern_identifier(identifier->string()); if (create_variables) generator.emit(interned_index, Bytecode::Op::EnvironmentMode::Lexical, false); - generator.emit_set_variable(*identifier, initialization_mode); - return Optional {}; + generator.emit_set_variable(*identifier, value, initialization_mode); + return {}; }, - [&](NonnullRefPtr const& pattern) -> Bytecode::CodeGenerationErrorOr> { - // Store the accumulator value in a permanent register - auto target_reg = generator.allocate_register(); - generator.emit(target_reg); - return generate_binding_pattern_bytecode(generator, pattern, initialization_mode, target_reg, create_variables); + [&](NonnullRefPtr const& pattern) -> Bytecode::CodeGenerationErrorOr { + return generate_binding_pattern_bytecode(generator, pattern, initialization_mode, value, create_variables); }, - [&](NonnullRefPtr const& expr) -> Bytecode::CodeGenerationErrorOr> { - return generator.emit_store_to_reference(*expr); + [&](NonnullRefPtr const& expr) -> Bytecode::CodeGenerationErrorOr { + (void)generator.emit_store_to_reference(*expr, value); + return {}; }); }; + auto temp_iterator_result = Bytecode::Operand(generator.allocate_register()); + for (auto& [name, alias, initializer, is_rest] : pattern.entries) { VERIFY(name.has()); if (is_rest) { VERIFY(!initializer); + auto value = Bytecode::Operand(generator.allocate_register()); + if (first) { // The iterator has not been called, and is thus known to be not exhausted - generator.emit(iterator_reg); - generator.emit(); + generator.emit(value, iterator); } else { auto& if_exhausted_block = generator.make_block(); auto& if_not_exhausted_block = generator.make_block(); auto& continuation_block = generator.make_block(); - generator.emit(is_iterator_exhausted_register); - generator.emit( + generator.emit( + is_iterator_exhausted, Bytecode::Label { if_exhausted_block }, Bytecode::Label { if_not_exhausted_block }); + value = Bytecode::Operand(generator.allocate_register()); + generator.switch_to_basic_block(if_exhausted_block); - generator.emit(); + generator.emit(value); generator.emit(Bytecode::Label { continuation_block }); generator.switch_to_basic_block(if_not_exhausted_block); - generator.emit(iterator_reg); - generator.emit(); + generator.emit(value, iterator); generator.emit(Bytecode::Label { continuation_block }); generator.switch_to_basic_block(continuation_block); } - return assign_accumulator_to_alias(alias); + return assign_value_to_alias(alias, value); } - // In the first iteration of the loop, a few things are true which can save - // us some bytecode: - // - the iterator result is still in the accumulator, so we can avoid a load - // - the iterator is not yet exhausted, which can save us a jump and some - // creation - auto& iterator_is_exhausted_block = generator.make_block(); if (!first) { auto& iterator_is_not_exhausted_block = generator.make_block(); - generator.emit(is_iterator_exhausted_register); - generator.emit( + generator.emit( + is_iterator_exhausted, Bytecode::Label { iterator_is_exhausted_block }, Bytecode::Label { iterator_is_not_exhausted_block }); generator.switch_to_basic_block(iterator_is_not_exhausted_block); - generator.emit(iterator_reg); } - generator.emit(); - generator.emit(temp_iterator_result_reg); - generator.emit_iterator_complete(); - generator.emit(is_iterator_exhausted_register); + generator.emit(temp_iterator_result, iterator); + generator.emit_iterator_complete(is_iterator_exhausted, temp_iterator_result); // We still have to check for exhaustion here. If the iterator is exhausted, // we need to bail before trying to get the value auto& no_bail_block = generator.make_block(); - generator.emit( + generator.emit( + is_iterator_exhausted, Bytecode::Label { iterator_is_exhausted_block }, Bytecode::Label { no_bail_block }); generator.switch_to_basic_block(no_bail_block); // Get the next value in the iterator - generator.emit(temp_iterator_result_reg); - generator.emit_iterator_value(); + auto value = Bytecode::Operand { generator.allocate_register() }; + generator.emit_iterator_value(value, temp_iterator_result); auto& create_binding_block = generator.make_block(); generator.emit(Bytecode::Label { create_binding_block }); // The iterator is exhausted, so we just load undefined and continue binding generator.switch_to_basic_block(iterator_is_exhausted_block); - generator.emit(js_undefined()); + generator.emit(value, generator.add_constant(js_undefined())); generator.emit(Bytecode::Label { create_binding_block }); - // Create the actual binding. The value which this entry must bind is now in the - // accumulator. We can proceed, processing the alias as a nested destructuring - // pattern if necessary. generator.switch_to_basic_block(create_binding_block); if (initializer) { @@ -1380,24 +1384,27 @@ static Bytecode::CodeGenerationErrorOr> generate_arr auto& value_is_not_undefined_block = generator.make_block(); generator.emit( + value, Bytecode::Label { value_is_undefined_block }, Bytecode::Label { value_is_not_undefined_block }); generator.switch_to_basic_block(value_is_undefined_block); + Optional default_value; if (auto const* alias_identifier = alias.get_pointer>()) { - TRY(generator.emit_named_evaluation_if_anonymous_function(*initializer, generator.intern_identifier((*alias_identifier)->string()))); + default_value = TRY(generator.emit_named_evaluation_if_anonymous_function(*initializer, generator.intern_identifier((*alias_identifier)->string()))).value(); } else if (auto const* name_identifier = name.get_pointer>()) { - TRY(generator.emit_named_evaluation_if_anonymous_function(*initializer, generator.intern_identifier((*name_identifier)->string()))); + default_value = TRY(generator.emit_named_evaluation_if_anonymous_function(*initializer, generator.intern_identifier((*name_identifier)->string()))).value(); } else { - FIXME_NEWBC TRY(initializer->generate_bytecode(generator)); + default_value = TRY(initializer->generate_bytecode(generator)).value(); } + generator.emit(value, *default_value); generator.emit(Bytecode::Label { value_is_not_undefined_block }); generator.switch_to_basic_block(value_is_not_undefined_block); } - FIXME_NEWBC TRY(assign_accumulator_to_alias(alias)); + TRY(assign_value_to_alias(alias, value)); first = false; } @@ -1405,88 +1412,100 @@ static Bytecode::CodeGenerationErrorOr> generate_arr auto& done_block = generator.make_block(); auto& not_done_block = generator.make_block(); - generator.emit(is_iterator_exhausted_register); - generator.emit( + generator.emit( + is_iterator_exhausted, Bytecode::Label { done_block }, Bytecode::Label { not_done_block }); generator.switch_to_basic_block(not_done_block); - generator.emit(iterator_reg); - generator.emit(Completion::Type::Normal, Optional {}); + generator.emit(iterator, Completion::Type::Normal, Optional {}); generator.emit(Bytecode::Label { done_block }); generator.switch_to_basic_block(done_block); - return Optional {}; + return {}; } -static Bytecode::CodeGenerationErrorOr> generate_binding_pattern_bytecode(Bytecode::Generator& generator, BindingPattern const& pattern, Bytecode::Op::SetVariable::InitializationMode initialization_mode, Bytecode::Register const& value_reg, bool create_variables, [[maybe_unused]] Optional preferred_dst) +static Bytecode::CodeGenerationErrorOr generate_binding_pattern_bytecode(Bytecode::Generator& generator, BindingPattern const& pattern, Bytecode::Op::SetVariable::InitializationMode initialization_mode, Bytecode::Operand const& input_value, bool create_variables) { if (pattern.kind == BindingPattern::Kind::Object) - return generate_object_binding_pattern_bytecode(generator, pattern, initialization_mode, value_reg, create_variables); + return generate_object_binding_pattern_bytecode(generator, pattern, initialization_mode, input_value, create_variables); - return generate_array_binding_pattern_bytecode(generator, pattern, initialization_mode, value_reg, create_variables); + return generate_array_binding_pattern_bytecode(generator, pattern, initialization_mode, input_value, create_variables); } -static Bytecode::CodeGenerationErrorOr> assign_accumulator_to_variable_declarator(Bytecode::Generator& generator, VariableDeclarator const& declarator, VariableDeclaration const& declaration, [[maybe_unused]] Optional preferred_dst = {}) +static Bytecode::CodeGenerationErrorOr assign_value_to_variable_declarator(Bytecode::Generator& generator, VariableDeclarator const& declarator, VariableDeclaration const& declaration, Bytecode::Operand value) { auto initialization_mode = declaration.is_lexical_declaration() ? Bytecode::Op::SetVariable::InitializationMode::Initialize : Bytecode::Op::SetVariable::InitializationMode::Set; return declarator.target().visit( - [&](NonnullRefPtr const& id) -> Bytecode::CodeGenerationErrorOr> { - generator.emit_set_variable(*id, initialization_mode); - return Optional {}; + [&](NonnullRefPtr const& id) -> Bytecode::CodeGenerationErrorOr { + generator.emit_set_variable(*id, value, initialization_mode); + return {}; }, - [&](NonnullRefPtr const& pattern) -> Bytecode::CodeGenerationErrorOr> { - auto value_register = generator.allocate_register(); - generator.emit(value_register); - return generate_binding_pattern_bytecode(generator, pattern, initialization_mode, value_register, false); + [&](NonnullRefPtr const& pattern) -> Bytecode::CodeGenerationErrorOr { + return generate_binding_pattern_bytecode(generator, pattern, initialization_mode, value, false); }); } Bytecode::CodeGenerationErrorOr> VariableDeclaration::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - // The completion value of a VariableDeclaration is empty, but there might already be a non-empty - // completion value in the accumulator. We need to save it and restore it after the declaration executed. - auto saved_accumulator = generator.allocate_register(); - generator.emit(saved_accumulator); for (auto& declarator : m_declarations) { - if (declarator->init()) { - if (auto const* lhs = declarator->target().get_pointer>()) { - TRY(generator.emit_named_evaluation_if_anonymous_function(*declarator->init(), generator.intern_identifier((*lhs)->string()))); - } else { - FIXME_NEWBC TRY(declarator->init()->generate_bytecode(generator)); + Optional init_dst; + if (auto const* identifier = declarator->target().get_pointer>()) { + if ((*identifier)->is_local()) { + init_dst = Bytecode::Operand(Bytecode::Operand::Type::Local, (*identifier)->local_variable_index()); } - FIXME_NEWBC TRY(assign_accumulator_to_variable_declarator(generator, declarator, *this)); + } + + if (declarator->init()) { + auto value = TRY([&]() -> Bytecode::CodeGenerationErrorOr { + if (auto const* lhs = declarator->target().get_pointer>()) { + return TRY(generator.emit_named_evaluation_if_anonymous_function(*declarator->init(), generator.intern_identifier((*lhs)->string()), init_dst)).value(); + } else { + return TRY(declarator->init()->generate_bytecode(generator, init_dst)).value(); + } + }()); + (void)TRY(assign_value_to_variable_declarator(generator, declarator, *this, value)); } else if (m_declaration_kind != DeclarationKind::Var) { - generator.emit(js_undefined()); - FIXME_NEWBC TRY(assign_accumulator_to_variable_declarator(generator, declarator, *this)); + (void)TRY(assign_value_to_variable_declarator(generator, declarator, *this, generator.add_constant(js_undefined()))); } } - generator.emit(saved_accumulator); + for (auto& declarator : m_declarations) { + if (auto const* identifier = declarator->target().get_pointer>()) { + if ((*identifier)->is_local()) { + generator.set_local_initialized((*identifier)->local_variable_index()); + } + } + } + + // NOTE: VariableDeclaration doesn't return a completion value. return Optional {}; } -static Bytecode::CodeGenerationErrorOr> get_base_and_value_from_member_expression(Bytecode::Generator& generator, MemberExpression const& member_expression, Bytecode::Register this_reg, [[maybe_unused]] Optional preferred_dst = {}) +struct BaseAndValue { + Bytecode::Operand base; + Bytecode::Operand value; +}; + +static Bytecode::CodeGenerationErrorOr get_base_and_value_from_member_expression(Bytecode::Generator& generator, MemberExpression const& member_expression) { // https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation if (is(member_expression.object())) { // 1. Let env be GetThisEnvironment(). // 2. Let actualThis be ? env.GetThisBinding(). - generator.emit(); - generator.emit(this_reg); + auto this_value = Bytecode::Operand(generator.allocate_register()); + generator.emit(this_value); - Optional computed_property_value_register; + Optional computed_property; if (member_expression.is_computed()) { // SuperProperty : super [ Expression ] // 3. Let propertyNameReference be ? Evaluation of Expression. // 4. Let propertyNameValue be ? GetValue(propertyNameReference). - FIXME_NEWBC TRY(member_expression.property().generate_bytecode(generator)); - computed_property_value_register = generator.allocate_register(); - generator.emit(*computed_property_value_register); + computed_property = TRY(member_expression.property().generate_bytecode(generator)); } // 5/7. Return ? MakeSuperPropertyReference(actualThis, propertyKey, strict). @@ -1495,79 +1514,99 @@ static Bytecode::CodeGenerationErrorOr> get_base_and // 1. Let env be GetThisEnvironment(). // 2. Assert: env.HasSuperBinding() is true. // 3. Let baseValue be ? env.GetSuperBase(). - generator.emit(); + auto super_base = Bytecode::Operand(generator.allocate_register()); + generator.emit(super_base); + + auto value = Bytecode::Operand { generator.allocate_register() }; // 4. Return the Reference Record { [[Base]]: baseValue, [[ReferencedName]]: propertyKey, [[Strict]]: strict, [[ThisValue]]: actualThis }. - if (computed_property_value_register.has_value()) { + if (computed_property.has_value()) { // 5. Let propertyKey be ? ToPropertyKey(propertyNameValue). // FIXME: This does ToPropertyKey out of order, which is observable by Symbol.toPrimitive! - auto super_base_register = generator.allocate_register(); - generator.emit(super_base_register); - generator.emit(*computed_property_value_register); - generator.emit(super_base_register, this_reg); + generator.emit(value, super_base, *computed_property, this_value); } else { // 3. Let propertyKey be StringValue of IdentifierName. auto identifier_table_ref = generator.intern_identifier(verify_cast(member_expression.property()).string()); - generator.emit_get_by_id_with_this(identifier_table_ref, this_reg); - } - } else { - FIXME_NEWBC TRY(member_expression.object().generate_bytecode(generator)); - generator.emit(this_reg); - if (member_expression.is_computed()) { - FIXME_NEWBC TRY(member_expression.property().generate_bytecode(generator)); - generator.emit(this_reg); - } else if (is(member_expression.property())) { - generator.emit(generator.intern_identifier(verify_cast(member_expression.property()).string())); - } else { - generator.emit_get_by_id(generator.intern_identifier(verify_cast(member_expression.property()).string())); + generator.emit_get_by_id_with_this(value, super_base, identifier_table_ref, this_value); } + + return BaseAndValue { this_value, value }; } - return Optional {}; + auto base = TRY(member_expression.object().generate_bytecode(generator)).value(); + auto value = Bytecode::Operand { generator.allocate_register() }; + if (member_expression.is_computed()) { + auto property = TRY(member_expression.property().generate_bytecode(generator)).value(); + generator.emit(value, base, property); + } else if (is(member_expression.property())) { + generator.emit( + value, + base, + generator.intern_identifier(verify_cast(member_expression.property()).string())); + } else { + generator.emit_get_by_id(value, base, generator.intern_identifier(verify_cast(member_expression.property()).string())); + } + + return BaseAndValue { base, value }; } -static Bytecode::CodeGenerationErrorOr> generate_optional_chain(Bytecode::Generator& generator, OptionalChain const& optional_chain, Bytecode::Register current_value_register, Bytecode::Register current_base_register, [[maybe_unused]] Optional preferred_dst = {}); +static Bytecode::CodeGenerationErrorOr generate_optional_chain(Bytecode::Generator& generator, OptionalChain const& optional_chain, Bytecode::Operand current_value, Bytecode::Operand current_base, [[maybe_unused]] Optional preferred_dst = {}); -Bytecode::CodeGenerationErrorOr> CallExpression::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> CallExpression::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - auto callee_reg = generator.allocate_register(); - auto this_reg = generator.allocate_register(); - generator.emit(js_undefined()); - generator.emit(this_reg); Optional builtin; + Optional original_callee; + auto this_value = generator.add_constant(js_undefined()); + if (is(this)) { - FIXME_NEWBC TRY(m_callee->generate_bytecode(generator)); - generator.emit(callee_reg); + original_callee = TRY(m_callee->generate_bytecode(generator)).value(); } else if (is(*m_callee)) { auto& member_expression = static_cast(*m_callee); - FIXME_NEWBC TRY(get_base_and_value_from_member_expression(generator, member_expression, this_reg)); - generator.emit(callee_reg); + auto base_and_value = TRY(get_base_and_value_from_member_expression(generator, member_expression)); + original_callee = base_and_value.value; + this_value = base_and_value.base; builtin = Bytecode::get_builtin(member_expression); } else if (is(*m_callee)) { auto& optional_chain = static_cast(*m_callee); - FIXME_NEWBC TRY(generate_optional_chain(generator, optional_chain, callee_reg, this_reg)); + original_callee = Bytecode::Operand(generator.allocate_register()); + this_value = Bytecode::Operand(generator.allocate_register()); + TRY(generate_optional_chain(generator, optional_chain, *original_callee, this_value)); } else if (is(*m_callee)) { - // If the callee is an identifier, we may need to extract a `this` value. + // If the original_callee is an identifier, we may need to extract a `this` value. // This is important when we're inside a `with` statement and calling a method on // the environment's binding object. // NOTE: If the identifier refers to a known "local" or "global", we know it can't be // a `with` binding, so we can skip this. auto& identifier = static_cast(*m_callee); - if (!identifier.is_local() && !identifier.is_global()) { - generator.emit(generator.intern_identifier(identifier.string()), callee_reg, this_reg, generator.next_environment_variable_cache()); + if (identifier.is_local()) { + auto local = Bytecode::Operand(Bytecode::Operand::Type::Local, identifier.local_variable_index()); + if (!generator.is_local_initialized(local.index())) { + generator.emit(local); + } + original_callee = local; + } else if (identifier.is_global()) { + original_callee = m_callee->generate_bytecode(generator).value(); } else { - FIXME_NEWBC TRY(m_callee->generate_bytecode(generator)); - generator.emit(callee_reg); + original_callee = Bytecode::Operand(generator.allocate_register()); + this_value = Bytecode::Operand(generator.allocate_register()); + generator.emit( + *original_callee, + this_value, + generator.intern_identifier(identifier.string()), + generator.next_environment_variable_cache()); } } else { // FIXME: this = global object in sloppy mode. - FIXME_NEWBC TRY(m_callee->generate_bytecode(generator)); - generator.emit(callee_reg); + original_callee = TRY(m_callee->generate_bytecode(generator)).value(); } + // NOTE: We copy the callee to a new register to avoid overwriting it while evaluating arguments. + auto callee = Bytecode::Operand(generator.allocate_register()); + generator.emit(callee, *original_callee); + Bytecode::Op::CallType call_type; if (is(*this)) { call_type = Bytecode::Op::CallType::Construct; @@ -1582,10 +1621,11 @@ Bytecode::CodeGenerationErrorOr> CallExpression::gen expression_string_index = generator.intern_string(expression_string.release_value()); bool has_spread = any_of(arguments(), [](auto& argument) { return argument.is_spread; }); + auto dst = choose_dst(generator, preferred_dst); if (has_spread) { - FIXME_NEWBC TRY(arguments_to_array_for_call(generator, arguments())); - generator.emit(call_type, callee_reg, this_reg, expression_string_index); + auto arguments = TRY(arguments_to_array_for_call(generator, this->arguments())).value(); + generator.emit(call_type, dst, callee, this_value, arguments, expression_string_index); } else { Optional first_argument_reg {}; for (size_t i = 0; i < arguments().size(); ++i) { @@ -1595,27 +1635,37 @@ Bytecode::CodeGenerationErrorOr> CallExpression::gen } u32 register_offset = 0; for (auto const& argument : arguments()) { - FIXME_NEWBC TRY(argument.value->generate_bytecode(generator)); - generator.emit(Bytecode::Register { first_argument_reg.value().index() + register_offset }); + auto value = TRY(argument.value->generate_bytecode(generator)).value(); + generator.emit(Bytecode::Operand(Bytecode::Register(first_argument_reg.value().index() + register_offset)), value); register_offset += 1; } - generator.emit(call_type, callee_reg, this_reg, first_argument_reg.value_or(Bytecode::Register { 0 }), arguments().size(), expression_string_index, builtin); + generator.emit(call_type, dst, callee, this_value, first_argument_reg.value_or(Bytecode::Register { 0 }), arguments().size(), expression_string_index, builtin); } - return Optional {}; + return dst; } -static void generate_await(Bytecode::Generator& generator, Bytecode::Register received_completion_register, Bytecode::Register received_completion_type_register, Bytecode::Register received_completion_value_register, Bytecode::IdentifierTableIndex type_identifier, Bytecode::IdentifierTableIndex value_identifier); +static Bytecode::Operand generate_await( + Bytecode::Generator& generator, + Bytecode::Operand argument, + Bytecode::Operand received_completion, + Bytecode::Operand received_completion_type, + Bytecode::Operand received_completion_value, + Bytecode::IdentifierTableIndex type_identifier, + Bytecode::IdentifierTableIndex value_identifier); // https://tc39.es/ecma262/#sec-return-statement-runtime-semantics-evaluation -Bytecode::CodeGenerationErrorOr> ReturnStatement::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> ReturnStatement::generate_bytecode(Bytecode::Generator& generator, Optional) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); + + Optional return_value; + if (m_argument) { // ReturnStatement : return Expression ; // 1. Let exprRef be ? Evaluation of Expression. // 2. Let exprValue be ? GetValue(exprRef). - FIXME_NEWBC TRY(m_argument->generate_bytecode(generator)); + return_value = TRY(m_argument->generate_bytecode(generator)).value(); // 3. If GetGeneratorKind() is async, set exprValue to ? Await(exprValue). // Spec Issue?: The spec doesn't seem to do implicit await on explicit return for async functions, but does for @@ -1624,44 +1674,43 @@ Bytecode::CodeGenerationErrorOr> ReturnStatement::ge // See: https://tc39.es/ecma262/#sec-asyncblockstart // c. Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done. if (generator.is_in_async_function()) { - auto received_completion_register = generator.allocate_register(); - auto received_completion_type_register = generator.allocate_register(); - auto received_completion_value_register = generator.allocate_register(); + auto received_completion = Bytecode::Operand(generator.allocate_register()); + auto received_completion_type = Bytecode::Operand(generator.allocate_register()); + auto received_completion_value = Bytecode::Operand(generator.allocate_register()); auto type_identifier = generator.intern_identifier("type"); auto value_identifier = generator.intern_identifier("value"); - generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier); + return_value = generate_await(generator, *return_value, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier); } // 4. Return Completion Record { [[Type]]: return, [[Value]]: exprValue, [[Target]]: empty }. } else { // ReturnStatement : return ; // 1. Return Completion Record { [[Type]]: return, [[Value]]: undefined, [[Target]]: empty }. - generator.emit(js_undefined()); + return_value = generator.add_constant(js_undefined()); } if (generator.is_in_generator_or_async_function()) { generator.perform_needed_unwinds(); - generator.emit(nullptr); + generator.emit(nullptr, *return_value); } else { generator.perform_needed_unwinds(); - generator.emit(); + generator.emit(return_value); } - return Optional {}; + return return_value; } -static void get_received_completion_type_and_value(Bytecode::Generator& generator, Bytecode::Register received_completion_register, Bytecode::Register received_completion_type_register, Bytecode::Register received_completion_value_register, Bytecode::IdentifierTableIndex type_identifier, Bytecode::IdentifierTableIndex value_identifier) +static void get_received_completion_type_and_value( + Bytecode::Generator& generator, + Bytecode::Operand received_completion, + Bytecode::Operand received_completion_type, + Bytecode::Operand received_completion_value, + Bytecode::IdentifierTableIndex type_identifier, + Bytecode::IdentifierTableIndex value_identifier) { - // The accumulator is set to an object, for example: { "type": 1 (normal), value: 1337 } - generator.emit(received_completion_register); - - generator.emit_get_by_id(type_identifier); - generator.emit(received_completion_type_register); - - generator.emit(received_completion_register); - generator.emit_get_by_id(value_identifier); - generator.emit(received_completion_value_register); + generator.emit_get_by_id(received_completion_type, received_completion, type_identifier); + generator.emit_get_by_id(received_completion_value, received_completion, value_identifier); } enum class AwaitBeforeYield { @@ -1669,55 +1718,71 @@ enum class AwaitBeforeYield { Yes, }; -static void generate_yield(Bytecode::Generator& generator, Bytecode::Label continuation_label, Bytecode::Register received_completion_register, Bytecode::Register received_completion_type_register, Bytecode::Register received_completion_value_register, Bytecode::IdentifierTableIndex type_identifier, Bytecode::IdentifierTableIndex value_identifier, AwaitBeforeYield await_before_yield) +static void generate_yield(Bytecode::Generator& generator, + Bytecode::Label continuation_label, + Bytecode::Operand argument, + Bytecode::Operand received_completion, + Bytecode::Operand received_completion_type, + Bytecode::Operand received_completion_value, + Bytecode::IdentifierTableIndex type_identifier, + Bytecode::IdentifierTableIndex value_identifier, + AwaitBeforeYield await_before_yield) { if (!generator.is_in_async_generator_function()) { - generator.emit(Bytecode::Label { continuation_label }); + generator.emit(Bytecode::Label { continuation_label }, argument); return; } if (await_before_yield == AwaitBeforeYield::Yes) - generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier); + argument = generate_await(generator, argument, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier); auto& unwrap_yield_resumption_block = generator.make_block(); - generator.emit(Bytecode::Label { unwrap_yield_resumption_block }); + generator.emit(Bytecode::Label { unwrap_yield_resumption_block }, argument); generator.switch_to_basic_block(unwrap_yield_resumption_block); - get_received_completion_type_and_value(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier); + + generator.emit(received_completion, Bytecode::Operand(Bytecode::Register(0))); + get_received_completion_type_and_value(generator, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier); // 27.6.3.7 AsyncGeneratorUnwrapYieldResumption ( resumptionValue ), https://tc39.es/ecma262/#sec-asyncgeneratorunwrapyieldresumption // 1. If resumptionValue.[[Type]] is not return, return ? resumptionValue. - auto& load_completion_and_jump_to_continuation_label_block = generator.make_block(); auto& resumption_value_type_is_return_block = generator.make_block(); - generator.emit(Value(to_underlying(Completion::Type::Return))); - generator.emit(received_completion_type_register); - generator.emit( - Bytecode::Label { load_completion_and_jump_to_continuation_label_block }, + auto resumption_value_type_is_not_return_result = Bytecode::Operand(generator.allocate_register()); + generator.emit( + resumption_value_type_is_not_return_result, + received_completion_type, + generator.add_constant(Value(to_underlying(Completion::Type::Return)))); + generator.emit( + resumption_value_type_is_not_return_result, + Bytecode::Label { continuation_label }, Bytecode::Label { resumption_value_type_is_return_block }); generator.switch_to_basic_block(resumption_value_type_is_return_block); // 2. Let awaited be Completion(Await(resumptionValue.[[Value]])). - generator.emit(received_completion_value_register); - generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier); + generate_await(generator, received_completion_value, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier); // 3. If awaited.[[Type]] is throw, return ? awaited. auto& awaited_type_is_normal_block = generator.make_block(); - generator.emit(Value(to_underlying(Completion::Type::Throw))); - generator.emit(received_completion_type_register); - generator.emit( - Bytecode::Label { load_completion_and_jump_to_continuation_label_block }, + auto awaited_type_is_throw_result = Bytecode::Operand(generator.allocate_register()); + generator.emit( + awaited_type_is_throw_result, + received_completion_type, + generator.add_constant(Value(to_underlying(Completion::Type::Throw)))); + generator.emit( + awaited_type_is_throw_result, + Bytecode::Label { continuation_label }, Bytecode::Label { awaited_type_is_normal_block }); // 4. Assert: awaited.[[Type]] is normal. generator.switch_to_basic_block(awaited_type_is_normal_block); // 5. Return Completion Record { [[Type]]: return, [[Value]]: awaited.[[Value]], [[Target]]: empty }. - generator.emit(Value(to_underlying(Completion::Type::Return))); - generator.emit(received_completion_register, type_identifier, Bytecode::Op::PropertyKind::KeyValue, generator.next_property_lookup_cache()); - generator.emit(Bytecode::Label { load_completion_and_jump_to_continuation_label_block }); - - generator.switch_to_basic_block(load_completion_and_jump_to_continuation_label_block); - generator.emit(received_completion_register); + generator.emit( + received_completion, + type_identifier, + generator.add_constant(Value(to_underlying(Completion::Type::Return))), + Bytecode::Op::PropertyKind::KeyValue, + generator.next_property_lookup_cache()); generator.emit(continuation_label); } @@ -1726,9 +1791,9 @@ Bytecode::CodeGenerationErrorOr> YieldExpression::ge Bytecode::Generator::SourceLocationScope scope(generator, *this); VERIFY(generator.is_in_generator_function()); - auto received_completion_register = generator.allocate_register(); - auto received_completion_type_register = generator.allocate_register(); - auto received_completion_value_register = generator.allocate_register(); + auto received_completion = Bytecode::Operand(generator.allocate_register()); + auto received_completion_type = Bytecode::Operand(generator.allocate_register()); + auto received_completion_value = Bytecode::Operand(generator.allocate_register()); auto type_identifier = generator.intern_identifier("type"); auto value_identifier = generator.intern_identifier("value"); @@ -1741,29 +1806,26 @@ Bytecode::CodeGenerationErrorOr> YieldExpression::ge // 2. Let exprRef be ? Evaluation of AssignmentExpression. // 3. Let value be ? GetValue(exprRef). VERIFY(m_argument); - FIXME_NEWBC TRY(m_argument->generate_bytecode(generator)); + auto value = TRY(m_argument->generate_bytecode(generator)).value(); // 4. Let iteratorRecord be ? GetIterator(value, generatorKind). - auto iterator_record_register = generator.allocate_register(); + auto iterator_record = Bytecode::Operand(generator.allocate_register()); auto iterator_hint = generator.is_in_async_generator_function() ? IteratorHint::Async : IteratorHint::Sync; - generator.emit(iterator_hint); - generator.emit(iterator_record_register); + generator.emit(iterator_record, value, iterator_hint); // 5. Let iterator be iteratorRecord.[[Iterator]]. - auto iterator_register = generator.allocate_register(); - generator.emit(iterator_register, iterator_record_register); + auto iterator = Bytecode::Operand(generator.allocate_register()); + generator.emit(iterator, iterator_record); // Cache iteratorRecord.[[NextMethod]] for use in step 7.a.i. - auto next_method_register = generator.allocate_register(); - generator.emit(next_method_register, iterator_record_register); + auto next_method = Bytecode::Operand(generator.allocate_register()); + generator.emit(next_method, iterator_record); // 6. Let received be NormalCompletion(undefined). // See get_received_completion_type_and_value above. - generator.emit(Value(to_underlying(Completion::Type::Normal))); - generator.emit(received_completion_type_register); + generator.emit(received_completion_type, generator.add_constant(Value(to_underlying(Completion::Type::Normal)))); - generator.emit(js_undefined()); - generator.emit(received_completion_value_register); + generator.emit(received_completion_value, generator.add_constant(js_undefined())); // 7. Repeat, auto& loop_block = generator.make_block(); @@ -1777,129 +1839,149 @@ Bytecode::CodeGenerationErrorOr> YieldExpression::ge auto& type_is_normal_block = generator.make_block(); auto& is_type_throw_block = generator.make_block(); - generator.emit(Value(to_underlying(Completion::Type::Normal))); - generator.emit(received_completion_type_register); - generator.emit( + auto received_completion_type_register_is_normal = Bytecode::Operand(generator.allocate_register()); + generator.emit( + received_completion_type_register_is_normal, + received_completion_type, + generator.add_constant(Value(to_underlying(Completion::Type::Normal)))); + generator.emit( + received_completion_type_register_is_normal, Bytecode::Label { type_is_normal_block }, Bytecode::Label { is_type_throw_block }); generator.switch_to_basic_block(type_is_normal_block); // i. Let innerResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « received.[[Value]] »). - generator.emit_with_extra_register_slots(2, AK::Array { received_completion_value_register, received_completion_value_register }); - generator.emit(Bytecode::Op::CallType::Call, next_method_register, iterator_register); + auto array = Bytecode::Operand(generator.allocate_register()); + generator.emit_with_extra_operand_slots(2u, array, AK::Array { received_completion_value, received_completion_value }); + auto inner_result = Bytecode::Operand(generator.allocate_register()); + generator.emit(Bytecode::Op::CallType::Call, inner_result, next_method, iterator, array); // ii. If generatorKind is async, set innerResult to ? Await(innerResult). - if (generator.is_in_async_generator_function()) - generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier); + if (generator.is_in_async_generator_function()) { + auto new_inner_result = generate_await(generator, inner_result, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier); + generator.emit(inner_result, new_inner_result); + } // iii. If innerResult is not an Object, throw a TypeError exception. - generator.emit(); - - auto inner_result_register = generator.allocate_register(); - generator.emit(inner_result_register); + generator.emit(inner_result); // iv. Let done be ? IteratorComplete(innerResult). - generator.emit_iterator_complete(); + auto done = Bytecode::Operand(generator.allocate_register()); + generator.emit_iterator_complete(done, inner_result); // v. If done is true, then auto& type_is_normal_done_block = generator.make_block(); auto& type_is_normal_not_done_block = generator.make_block(); - generator.emit( + generator.emit( + done, Bytecode::Label { type_is_normal_done_block }, Bytecode::Label { type_is_normal_not_done_block }); generator.switch_to_basic_block(type_is_normal_done_block); // 1. Return ? IteratorValue(innerResult). - generator.emit(inner_result_register); - generator.emit_iterator_value(); + auto return_value = Bytecode::Operand(generator.allocate_register()); + generator.emit_iterator_value(return_value, inner_result); generator.emit(Bytecode::Label { loop_end_block }); generator.switch_to_basic_block(type_is_normal_not_done_block); // vi. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerResult))). // vii. Else, set received to Completion(GeneratorYield(innerResult)). - generator.emit(inner_result_register); - // FIXME: Yield currently only accepts a Value, not an object conforming to the IteratorResult interface, so we have to do an observable lookup of `value` here. - // This only matters for non-async generators. - generator.emit_iterator_value(); + { + // FIXME: Yield currently only accepts a Value, not an object conforming to the IteratorResult interface, so we have to do an observable lookup of `value` here. + // This only matters for non-async generators. + auto current_value = Bytecode::Operand(generator.allocate_register()); + generator.emit_iterator_value(current_value, inner_result); - generate_yield(generator, Bytecode::Label { continuation_block }, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier, AwaitBeforeYield::No); + generate_yield(generator, + Bytecode::Label { continuation_block }, + current_value, + received_completion, + received_completion_type, + received_completion_value, + type_identifier, + value_identifier, + AwaitBeforeYield::No); + } // b. Else if received.[[Type]] is throw, then generator.switch_to_basic_block(is_type_throw_block); auto& type_is_throw_block = generator.make_block(); auto& type_is_return_block = generator.make_block(); - generator.emit(Value(to_underlying(Completion::Type::Throw))); - generator.emit(received_completion_type_register); - generator.emit( + auto received_completion_type_register_is_throw = Bytecode::Operand(generator.allocate_register()); + generator.emit( + received_completion_type_register_is_throw, + Bytecode::Operand(received_completion_type), + generator.add_constant(Value(to_underlying(Completion::Type::Throw)))); + generator.emit( + received_completion_type_register_is_throw, Bytecode::Label { type_is_throw_block }, Bytecode::Label { type_is_return_block }); generator.switch_to_basic_block(type_is_throw_block); // i. Let throw be ? GetMethod(iterator, "throw"). - auto throw_method_register = generator.allocate_register(); - auto throw_identifier = generator.intern_identifier("throw"); - generator.emit(iterator_register); - generator.emit(throw_identifier); - generator.emit(throw_method_register); + auto throw_method = Bytecode::Operand(generator.allocate_register()); + generator.emit(throw_method, iterator, generator.intern_identifier("throw")); // ii. If throw is not undefined, then auto& throw_method_is_defined_block = generator.make_block(); auto& throw_method_is_undefined_block = generator.make_block(); - generator.emit(js_undefined()); - generator.emit(throw_method_register); - generator.emit( - Bytecode::Label { throw_method_is_defined_block }, - Bytecode::Label { throw_method_is_undefined_block }); + generator.emit( + throw_method, + Bytecode::Label { throw_method_is_undefined_block }, + Bytecode::Label { throw_method_is_defined_block }); generator.switch_to_basic_block(throw_method_is_defined_block); // 1. Let innerResult be ? Call(throw, iterator, « received.[[Value]] »). - generator.emit_with_extra_register_slots(2, AK::Array { received_completion_value_register, received_completion_value_register }); - generator.emit(Bytecode::Op::CallType::Call, throw_method_register, iterator_register); + auto received_value_array = Bytecode::Operand(generator.allocate_register()); + generator.emit_with_extra_operand_slots(2u, received_value_array, AK::Array { received_completion_value, received_completion_value }); + generator.emit(Bytecode::Op::CallType::Call, inner_result, throw_method, iterator, received_value_array); // 2. If generatorKind is async, set innerResult to ? Await(innerResult). - if (generator.is_in_async_generator_function()) - generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier); + if (generator.is_in_async_generator_function()) { + auto new_result = generate_await(generator, inner_result, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier); + generator.emit(inner_result, new_result); + } // 3. NOTE: Exceptions from the inner iterator throw method are propagated. Normal completions from an inner throw method are processed similarly to an inner next. // 4. If innerResult is not an Object, throw a TypeError exception. - generator.emit(); - generator.emit(inner_result_register); + generator.emit(inner_result); // 5. Let done be ? IteratorComplete(innerResult). - generator.emit_iterator_complete(); + generator.emit_iterator_complete(done, inner_result); // 6. If done is true, then auto& type_is_throw_done_block = generator.make_block(); auto& type_is_throw_not_done_block = generator.make_block(); - generator.emit( + generator.emit( + done, Bytecode::Label { type_is_throw_done_block }, Bytecode::Label { type_is_throw_not_done_block }); generator.switch_to_basic_block(type_is_throw_done_block); // a. Return ? IteratorValue(innerResult). - generator.emit(inner_result_register); - generator.emit_iterator_value(); + generator.emit_iterator_value(return_value, inner_result); generator.emit(Bytecode::Label { loop_end_block }); generator.switch_to_basic_block(type_is_throw_not_done_block); - // 7. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerResult))). - // 8. Else, set received to Completion(GeneratorYield(innerResult)). - generator.emit(inner_result_register); + { + // 7. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerResult))). + // 8. Else, set received to Completion(GeneratorYield(innerResult)). - // FIXME: Yield currently only accepts a Value, not an object conforming to the IteratorResult interface, so we have to do an observable lookup of `value` here. - // This only matters for non-async generators. - generator.emit_iterator_value(); - - generate_yield(generator, Bytecode::Label { continuation_block }, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier, AwaitBeforeYield::No); + // FIXME: Yield currently only accepts a Value, not an object conforming to the IteratorResult interface, so we have to do an observable lookup of `value` here. + // This only matters for non-async generators. + auto yield_value = Bytecode::Operand(generator.allocate_register()); + generator.emit_iterator_value(yield_value, inner_result); + generate_yield(generator, Bytecode::Label { continuation_block }, yield_value, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier, AwaitBeforeYield::No); + } generator.switch_to_basic_block(throw_method_is_undefined_block); @@ -1907,126 +1989,130 @@ Bytecode::CodeGenerationErrorOr> YieldExpression::ge // 2. Let closeCompletion be Completion Record { [[Type]]: normal, [[Value]]: empty, [[Target]]: empty }. // 3. If generatorKind is async, perform ? AsyncIteratorClose(iteratorRecord, closeCompletion). - generator.emit(iterator_record_register); if (generator.is_in_async_generator_function()) { // FIXME: This performs `await` outside of the generator! - generator.emit(Completion::Type::Normal, Optional {}); + generator.emit(iterator_record, Completion::Type::Normal, Optional {}); } // 4. Else, perform ? IteratorClose(iteratorRecord, closeCompletion). else { - generator.emit(Completion::Type::Normal, Optional {}); + generator.emit(iterator_record, Completion::Type::Normal, Optional {}); } // 5. NOTE: The next step throws a TypeError to indicate that there was a yield* protocol violation: iterator does not have a throw method. // 6. Throw a TypeError exception. - generator.emit(generator.intern_string(ErrorType::YieldFromIteratorMissingThrowMethod.message())); + auto exception = Bytecode::Operand(generator.allocate_register()); + generator.emit(exception, generator.intern_string(ErrorType::YieldFromIteratorMissingThrowMethod.message())); generator.perform_needed_unwinds(); - generator.emit(); + generator.emit(exception); // c. Else, // i. Assert: received.[[Type]] is return. generator.switch_to_basic_block(type_is_return_block); // ii. Let return be ? GetMethod(iterator, "return"). - auto return_method_register = generator.allocate_register(); - auto return_identifier = generator.intern_identifier("return"); - generator.emit(iterator_register); - generator.emit(return_identifier); - generator.emit(return_method_register); + auto return_method = Bytecode::Operand(generator.allocate_register()); + generator.emit(return_method, iterator, generator.intern_identifier("return")); // iii. If return is undefined, then auto& return_is_undefined_block = generator.make_block(); auto& return_is_defined_block = generator.make_block(); - generator.emit(js_undefined()); - generator.emit(return_method_register); - generator.emit( + generator.emit( + return_method, Bytecode::Label { return_is_undefined_block }, Bytecode::Label { return_is_defined_block }); generator.switch_to_basic_block(return_is_undefined_block); // 1. If generatorKind is async, set received.[[Value]] to ? Await(received.[[Value]]). - generator.emit(received_completion_value_register); - if (generator.is_in_async_generator_function()) - generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier); + if (generator.is_in_async_generator_function()) { + generate_await(generator, received_completion_value, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier); + } // 2. Return ? received. // NOTE: This will always be a return completion. generator.perform_needed_unwinds(); - generator.emit(nullptr); + generator.emit(nullptr, received_completion_value); generator.switch_to_basic_block(return_is_defined_block); // iv. Let innerReturnResult be ? Call(return, iterator, « received.[[Value]] »). - generator.emit_with_extra_register_slots(2, AK::Array { received_completion_value_register, received_completion_value_register }); - generator.emit(Bytecode::Op::CallType::Call, return_method_register, iterator_register); + auto call_array = Bytecode::Operand(generator.allocate_register()); + generator.emit_with_extra_operand_slots(2, call_array, AK::Array { received_completion_value, received_completion_value }); + auto inner_return_result = Bytecode::Operand(generator.allocate_register()); + generator.emit(Bytecode::Op::CallType::Call, inner_return_result, return_method, iterator, call_array); // v. If generatorKind is async, set innerReturnResult to ? Await(innerReturnResult). - if (generator.is_in_async_generator_function()) - generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier); + if (generator.is_in_async_generator_function()) { + auto new_value = generate_await(generator, inner_return_result, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier); + generator.emit(inner_return_result, new_value); + } // vi. If innerReturnResult is not an Object, throw a TypeError exception. - generator.emit(); - - auto inner_return_result_register = generator.allocate_register(); - generator.emit(inner_return_result_register); + generator.emit(inner_return_result); // vii. Let done be ? IteratorComplete(innerReturnResult). - generator.emit_iterator_complete(); + generator.emit_iterator_complete(done, inner_return_result); // viii. If done is true, then auto& type_is_return_done_block = generator.make_block(); auto& type_is_return_not_done_block = generator.make_block(); - generator.emit( + generator.emit( + done, Bytecode::Label { type_is_return_done_block }, Bytecode::Label { type_is_return_not_done_block }); generator.switch_to_basic_block(type_is_return_done_block); // 1. Let value be ? IteratorValue(innerReturnResult). - generator.emit(inner_result_register); - generator.emit_iterator_value(); + auto inner_return_result_value = Bytecode::Operand(generator.allocate_register()); + generator.emit_iterator_value(inner_return_result_value, inner_return_result); // 2. Return Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. generator.perform_needed_unwinds(); - generator.emit(nullptr); + generator.emit(nullptr, inner_return_result_value); generator.switch_to_basic_block(type_is_return_not_done_block); // ix. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerReturnResult))). // x. Else, set received to Completion(GeneratorYield(innerReturnResult)). - generator.emit(inner_return_result_register); - // FIXME: Yield currently only accepts a Value, not an object conforming to the IteratorResult interface, so we have to do an observable lookup of `value` here. // This only matters for non-async generators. - generator.emit_iterator_value(); + auto received = Bytecode::Operand(generator.allocate_register()); + generator.emit_iterator_value(received, inner_return_result); - generate_yield(generator, Bytecode::Label { continuation_block }, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier, AwaitBeforeYield::No); + generate_yield(generator, Bytecode::Label { continuation_block }, received, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier, AwaitBeforeYield::No); generator.switch_to_basic_block(continuation_block); - get_received_completion_type_and_value(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier); + generator.emit(received_completion, Bytecode::Operand(Bytecode::Register(0))); + get_received_completion_type_and_value(generator, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier); generator.emit(Bytecode::Label { loop_block }); generator.switch_to_basic_block(loop_end_block); - return Optional {}; + return return_value; } + Optional argument; if (m_argument) - FIXME_NEWBC TRY(m_argument->generate_bytecode(generator)); + argument = TRY(m_argument->generate_bytecode(generator)).value(); else - generator.emit(js_undefined()); + argument = generator.add_constant(js_undefined()); auto& continuation_block = generator.make_block(); - generate_yield(generator, Bytecode::Label { continuation_block }, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier, AwaitBeforeYield::Yes); + generate_yield(generator, Bytecode::Label { continuation_block }, *argument, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier, AwaitBeforeYield::Yes); generator.switch_to_basic_block(continuation_block); - get_received_completion_type_and_value(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier); + generator.emit(received_completion, Bytecode::Operand(Bytecode::Register(0))); + get_received_completion_type_and_value(generator, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier); auto& normal_completion_continuation_block = generator.make_block(); auto& throw_completion_continuation_block = generator.make_block(); - generator.emit(Value(to_underlying(Completion::Type::Normal))); - generator.emit(received_completion_type_register); - generator.emit( + auto received_completion_type_is_normal = Bytecode::Operand(generator.allocate_register()); + generator.emit( + received_completion_type_is_normal, + received_completion_type, + generator.add_constant(Value(to_underlying(Completion::Type::Normal)))); + generator.emit( + received_completion_type_is_normal, Bytecode::Label { normal_completion_continuation_block }, Bytecode::Label { throw_completion_continuation_block }); @@ -2034,30 +2120,31 @@ Bytecode::CodeGenerationErrorOr> YieldExpression::ge auto& return_value_block = generator.make_block(); generator.switch_to_basic_block(throw_completion_continuation_block); - generator.emit(Value(to_underlying(Completion::Type::Throw))); - generator.emit(received_completion_type_register); + auto received_completion_type_is_throw = Bytecode::Operand(generator.allocate_register()); + generator.emit( + received_completion_type_is_throw, + received_completion_type, + generator.add_constant(Value(to_underlying(Completion::Type::Throw)))); // If type is not equal to "throw" or "normal", assume it's "return". - generator.emit( + generator.emit( + received_completion_type_is_throw, Bytecode::Label { throw_value_block }, Bytecode::Label { return_value_block }); generator.switch_to_basic_block(throw_value_block); - generator.emit(received_completion_value_register); generator.perform_needed_unwinds(); - generator.emit(); + generator.emit(received_completion_value); generator.switch_to_basic_block(return_value_block); - generator.emit(received_completion_value_register); generator.perform_needed_unwinds(); - generator.emit(nullptr); + generator.emit(nullptr, received_completion_value); generator.switch_to_basic_block(normal_completion_continuation_block); - generator.emit(received_completion_value_register); - return Optional {}; + return received_completion_value; } -Bytecode::CodeGenerationErrorOr> IfStatement::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> IfStatement::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); // test @@ -2072,28 +2159,38 @@ Bytecode::CodeGenerationErrorOr> IfStatement::genera auto& false_block = generator.make_block(); auto& end_block = generator.make_block(); - FIXME_NEWBC TRY(m_predicate->generate_bytecode(generator)); - generator.emit( + auto dst = choose_dst(generator, preferred_dst); + generator.emit(dst, generator.add_constant(js_undefined())); + + auto predicate = TRY(m_predicate->generate_bytecode(generator)).value(); + generator.emit( + predicate, Bytecode::Label { true_block }, Bytecode::Label { false_block }); generator.switch_to_basic_block(true_block); - generator.emit(js_undefined()); - FIXME_NEWBC TRY(m_consequent->generate_bytecode(generator)); + auto consequent = TRY(m_consequent->generate_bytecode(generator, dst)); if (!generator.is_current_block_terminated()) { + if (consequent.has_value()) + generator.emit(dst, *consequent); generator.emit(Bytecode::Label { end_block }); } generator.switch_to_basic_block(false_block); - generator.emit(js_undefined()); - if (m_alternate) - FIXME_NEWBC TRY(m_alternate->generate_bytecode(generator)); - if (!generator.is_current_block_terminated()) + Optional alternate; + if (m_alternate) { + alternate = TRY(m_alternate->generate_bytecode(generator, dst)); + } + if (!generator.is_current_block_terminated()) { + if (alternate.has_value()) + generator.emit(dst, *alternate); generator.emit(Bytecode::Label { end_block }); + } generator.switch_to_basic_block(end_block); - return Optional {}; + + return dst; } Bytecode::CodeGenerationErrorOr> ContinueStatement::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const @@ -2116,7 +2213,7 @@ Bytecode::CodeGenerationErrorOr> DebuggerStatement:: return Optional {}; } -Bytecode::CodeGenerationErrorOr> ConditionalExpression::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> ConditionalExpression::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); // test @@ -2131,56 +2228,62 @@ Bytecode::CodeGenerationErrorOr> ConditionalExpressi auto& false_block = generator.make_block(); auto& end_block = generator.make_block(); - FIXME_NEWBC TRY(m_test->generate_bytecode(generator)); - generator.emit( + auto test = TRY(m_test->generate_bytecode(generator)).value(); + generator.emit( + test, Bytecode::Label { true_block }, Bytecode::Label { false_block }); + auto dst = choose_dst(generator, preferred_dst); + generator.switch_to_basic_block(true_block); - FIXME_NEWBC TRY(m_consequent->generate_bytecode(generator)); + auto consequent = TRY(m_consequent->generate_bytecode(generator)).value(); + generator.emit(dst, consequent); + generator.emit(Bytecode::Label { end_block }); generator.switch_to_basic_block(false_block); - FIXME_NEWBC TRY(m_alternate->generate_bytecode(generator)); + auto alternate = TRY(m_alternate->generate_bytecode(generator)).value(); + generator.emit(dst, alternate); generator.emit(Bytecode::Label { end_block }); generator.switch_to_basic_block(end_block); - return Optional {}; + return dst; } Bytecode::CodeGenerationErrorOr> SequenceExpression::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - for (auto& expression : m_expressions) - FIXME_NEWBC TRY(expression->generate_bytecode(generator)); + Optional last_value; + for (auto& expression : m_expressions) { + last_value = TRY(expression->generate_bytecode(generator)); + } - return Optional {}; + return last_value; } -Bytecode::CodeGenerationErrorOr> TemplateLiteral::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> TemplateLiteral::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - auto string_reg = generator.allocate_register(); + + auto dst = choose_dst(generator, preferred_dst); for (size_t i = 0; i < m_expressions.size(); i++) { - FIXME_NEWBC TRY(m_expressions[i]->generate_bytecode(generator)); + auto value = TRY(m_expressions[i]->generate_bytecode(generator)).value(); if (i == 0) { - generator.emit(string_reg); + generator.emit(dst, value); } else { - generator.emit(string_reg); + generator.emit(dst, value); } } - generator.emit(string_reg); - return Optional {}; + return dst; } -Bytecode::CodeGenerationErrorOr> TaggedTemplateLiteral::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> TaggedTemplateLiteral::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - FIXME_NEWBC TRY(m_tag->generate_bytecode(generator)); - auto tag_reg = generator.allocate_register(); - generator.emit(tag_reg); + auto tag = TRY(m_tag->generate_bytecode(generator)).value(); // FIXME: We only need to record the first and last register, // due to packing everything in an array, same goes for argument_regs @@ -2190,12 +2293,12 @@ Bytecode::CodeGenerationErrorOr> TaggedTemplateLiter // * cache this somehow // * add a raw object accessor // * freeze array and raw member - Vector string_regs; + Vector string_regs; auto& expressions = m_template_literal->expressions(); for (size_t i = 0; i < expressions.size(); ++i) { if (i % 2 != 0) continue; - string_regs.append(generator.allocate_register()); + string_regs.append(Bytecode::Operand(generator.allocate_register())); } size_t reg_index = 0; @@ -2205,102 +2308,97 @@ Bytecode::CodeGenerationErrorOr> TaggedTemplateLiter // NOTE: If the string contains invalid escapes we get a null expression here, // which we then convert to the expected `undefined` TV. See // 12.9.6.1 Static Semantics: TV, https://tc39.es/ecma262/#sec-static-semantics-tv - if (is(expressions[i])) - generator.emit(js_undefined()); - else - FIXME_NEWBC TRY(expressions[i]->generate_bytecode(generator)); - - auto string_reg = string_regs[reg_index++]; - generator.emit(string_reg); + auto string_reg = Bytecode::Operand(string_regs[reg_index++]); + if (is(expressions[i])) { + generator.emit(string_reg, generator.add_constant(js_undefined())); + } else { + auto value = TRY(expressions[i]->generate_bytecode(generator)).value(); + generator.emit(string_reg, value); + } } + auto strings_array = Bytecode::Operand(generator.allocate_register()); if (string_regs.is_empty()) { - generator.emit(); + generator.emit(strings_array); } else { - generator.emit_with_extra_register_slots(2u, AK::Array { string_regs.first(), string_regs.last() }); + generator.emit_with_extra_operand_slots(2u, strings_array, AK::Array { string_regs.first(), string_regs.last() }); } - auto strings_reg = generator.allocate_register(); - generator.emit(strings_reg); - Vector argument_regs; - argument_regs.append(strings_reg); + Vector argument_regs; + argument_regs.append(strings_array); for (size_t i = 1; i < expressions.size(); i += 2) - argument_regs.append(generator.allocate_register()); + argument_regs.append(Bytecode::Operand(generator.allocate_register())); for (size_t i = 1; i < expressions.size(); i += 2) { auto string_reg = argument_regs[1 + i / 2]; - FIXME_NEWBC TRY(expressions[i]->generate_bytecode(generator)); - generator.emit(string_reg); + auto string = TRY(expressions[i]->generate_bytecode(generator)).value(); + generator.emit(string_reg, string); } - Vector raw_string_regs; + Vector raw_string_regs; for ([[maybe_unused]] auto& raw_string : m_template_literal->raw_strings()) - string_regs.append(generator.allocate_register()); + string_regs.append(Bytecode::Operand(generator.allocate_register())); reg_index = 0; for (auto& raw_string : m_template_literal->raw_strings()) { - FIXME_NEWBC TRY(raw_string->generate_bytecode(generator)); + auto value = TRY(raw_string->generate_bytecode(generator)).value(); auto raw_string_reg = string_regs[reg_index++]; - generator.emit(raw_string_reg); + generator.emit(raw_string_reg, value); raw_string_regs.append(raw_string_reg); } + auto raw_strings_array = Bytecode::Operand(generator.allocate_register()); if (raw_string_regs.is_empty()) { - generator.emit(); + generator.emit(raw_strings_array); } else { - generator.emit_with_extra_register_slots(2u, AK::Array { raw_string_regs.first(), raw_string_regs.last() }); + generator.emit_with_extra_operand_slots(2u, raw_strings_array, AK::Array { raw_string_regs.first(), raw_string_regs.last() }); } - auto raw_strings_reg = generator.allocate_register(); - generator.emit(raw_strings_reg); - generator.emit(strings_reg, generator.intern_identifier("raw"), Bytecode::Op::PropertyKind::KeyValue, generator.next_property_lookup_cache()); - - generator.emit(js_undefined()); - auto this_reg = generator.allocate_register(); - generator.emit(this_reg); + generator.emit(strings_array, generator.intern_identifier("raw"), raw_strings_array, Bytecode::Op::PropertyKind::KeyValue, generator.next_property_lookup_cache()); + auto arguments = Bytecode::Operand(generator.allocate_register()); if (!argument_regs.is_empty()) - generator.emit_with_extra_register_slots(2, AK::Array { argument_regs.first(), argument_regs.last() }); + generator.emit_with_extra_operand_slots(2, arguments, AK::Array { argument_regs.first(), argument_regs.last() }); else - generator.emit(); + generator.emit(arguments); - generator.emit(Bytecode::Op::CallType::Call, tag_reg, this_reg); - return Optional {}; + auto dst = choose_dst(generator, preferred_dst); + generator.emit(Bytecode::Op::CallType::Call, dst, tag, generator.add_constant(js_undefined()), arguments); + return dst; } -Bytecode::CodeGenerationErrorOr> UpdateExpression::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> UpdateExpression::generate_bytecode(Bytecode::Generator& generator, Optional) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - auto reference_registers = TRY(generator.emit_load_from_reference(*m_argument, Bytecode::Generator::CollectRegisters::Yes)); + auto reference = TRY(generator.emit_load_from_reference(*m_argument)); - Optional previous_value_for_postfix_reg; + Optional previous_value_for_postfix; if (!m_prefixed) { - previous_value_for_postfix_reg = generator.allocate_register(); - generator.emit(); - generator.emit(*previous_value_for_postfix_reg); + previous_value_for_postfix = Bytecode::Operand(generator.allocate_register()); + generator.emit(*previous_value_for_postfix, *reference.loaded_value); } if (m_op == UpdateOp::Increment) - generator.emit(); + generator.emit(*reference.loaded_value); else - generator.emit(); + generator.emit(*reference.loaded_value); - if (reference_registers.has_value()) - FIXME_NEWBC TRY(generator.emit_store_to_reference(*reference_registers)); + if (is(*m_argument)) + (void)TRY(generator.emit_store_to_reference(static_cast(*m_argument), *reference.loaded_value)); else - FIXME_NEWBC TRY(generator.emit_store_to_reference(*m_argument)); + (void)TRY(generator.emit_store_to_reference(reference, *reference.loaded_value)); if (!m_prefixed) - generator.emit(*previous_value_for_postfix_reg); - return Optional {}; + return *previous_value_for_postfix; + return *reference.loaded_value; } Bytecode::CodeGenerationErrorOr> ThrowStatement::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - FIXME_NEWBC TRY(m_argument->generate_bytecode(generator)); + auto argument = TRY(m_argument->generate_bytecode(generator)).value(); generator.perform_needed_unwinds(); - generator.emit(); + generator.emit(argument); return Optional {}; } @@ -2330,12 +2428,14 @@ Bytecode::CodeGenerationErrorOr> TryStatement::gener Bytecode::BasicBlock* next_block { nullptr }; + Optional dst; + if (m_finalizer) { // FIXME: See notes in Op.h->ScheduleJump auto& finalizer_block = generator.make_block(); generator.switch_to_basic_block(finalizer_block); generator.emit(); - FIXME_NEWBC TRY(m_finalizer->generate_bytecode(generator)); + (void)TRY(m_finalizer->generate_bytecode(generator)); if (!generator.is_current_block_terminated()) { next_block = &generator.make_block(); auto next_target = Bytecode::Label { *next_block }; @@ -2350,7 +2450,8 @@ Bytecode::CodeGenerationErrorOr> TryStatement::gener auto& handler_block = generator.make_block(); generator.switch_to_basic_block(handler_block); - generator.emit(); + auto caught_value = Bytecode::Operand { generator.allocate_register() }; + generator.emit(caught_value); if (!m_finalizer) generator.emit(); @@ -2358,33 +2459,29 @@ Bytecode::CodeGenerationErrorOr> TryStatement::gener // OPTIMIZATION: We avoid creating a lexical environment if the catch clause has no parameter. bool did_create_variable_scope_for_catch_clause = false; - FIXME_NEWBC TRY(m_handler->parameter().visit( - [&](DeprecatedFlyString const& parameter) -> Bytecode::CodeGenerationErrorOr> { + TRY(m_handler->parameter().visit( + [&](DeprecatedFlyString const& parameter) -> Bytecode::CodeGenerationErrorOr { if (!parameter.is_empty()) { generator.begin_variable_scope(); did_create_variable_scope_for_catch_clause = true; auto parameter_identifier = generator.intern_identifier(parameter); generator.emit(parameter_identifier, Bytecode::Op::EnvironmentMode::Lexical, false); - generator.emit(parameter_identifier, generator.next_environment_variable_cache(), Bytecode::Op::SetVariable::InitializationMode::Initialize); + generator.emit(parameter_identifier, caught_value, generator.next_environment_variable_cache(), Bytecode::Op::SetVariable::InitializationMode::Initialize); } - return Optional {}; + return {}; }, - [&](NonnullRefPtr const& binding_pattern) -> Bytecode::CodeGenerationErrorOr> { + [&](NonnullRefPtr const& binding_pattern) -> Bytecode::CodeGenerationErrorOr { generator.begin_variable_scope(); did_create_variable_scope_for_catch_clause = true; - - auto value_register = generator.allocate_register(); - generator.emit(value_register); - FIXME_NEWBC TRY(generate_binding_pattern_bytecode(generator, *binding_pattern, Bytecode::Op::SetVariable::InitializationMode::Initialize, value_register, true)); - return Optional {}; + TRY(generate_binding_pattern_bytecode(generator, *binding_pattern, Bytecode::Op::SetVariable::InitializationMode::Initialize, caught_value, true)); + return {}; })); - // Set accumulator to undefined, otherwise we leak the error object through the accumulator. - // For example: `try { BigInt.call() } catch {}` would result in the error object. Note that - // the exception _is_ caught here, it just leaks the error object through to the result. - generator.emit(js_undefined()); - - FIXME_NEWBC TRY(m_handler->body().generate_bytecode(generator)); + auto handler_result = TRY(m_handler->body().generate_bytecode(generator)); + if (handler_result.has_value() && !generator.is_current_block_terminated()) { + dst = Bytecode::Operand(generator.allocate_register()); + generator.emit(*dst, *handler_result); + } handler_target = Bytecode::Label { handler_block }; if (did_create_variable_scope_for_catch_clause) @@ -2418,8 +2515,13 @@ Bytecode::CodeGenerationErrorOr> TryStatement::gener generator.start_boundary(Bytecode::Generator::BlockBoundaryType::ReturnToFinally); generator.switch_to_basic_block(target_block); - FIXME_NEWBC TRY(m_block->generate_bytecode(generator)); + auto block_result = TRY(m_block->generate_bytecode(generator)); if (!generator.is_current_block_terminated()) { + if (block_result.has_value()) { + dst = Bytecode::Operand(generator.allocate_register()); + generator.emit(*dst, *block_result); + } + if (m_finalizer) { generator.emit(*finalizer_target); } else { @@ -2437,7 +2539,9 @@ Bytecode::CodeGenerationErrorOr> TryStatement::gener generator.end_boundary(Bytecode::Generator::BlockBoundaryType::Unwind); generator.switch_to_basic_block(next_block ? *next_block : saved_block); - return Optional {}; + if (!dst.has_value()) + return generator.add_constant(js_undefined()); + return dst; } Bytecode::CodeGenerationErrorOr> SwitchStatement::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const @@ -2449,9 +2553,7 @@ Bytecode::CodeGenerationErrorOr> SwitchStatement::ge Bytecode::CodeGenerationErrorOr> SwitchStatement::generate_labelled_evaluation(Bytecode::Generator& generator, Vector const& label_set, [[maybe_unused]] Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - auto discriminant_reg = generator.allocate_register(); - FIXME_NEWBC TRY(m_discriminant->generate_bytecode(generator)); - generator.emit(discriminant_reg); + auto discriminant = TRY(m_discriminant->generate_bytecode(generator)).value(); Vector case_blocks; Bytecode::BasicBlock* entry_block_for_default { nullptr }; Bytecode::BasicBlock* next_test_block = &generator.make_block(); @@ -2467,10 +2569,12 @@ Bytecode::CodeGenerationErrorOr> SwitchStatement::ge auto& case_entry_block = generator.make_block(); if (switch_case->test()) { generator.switch_to_basic_block(*next_test_block); - FIXME_NEWBC TRY(switch_case->test()->generate_bytecode(generator)); - generator.emit(discriminant_reg); + auto test_value = TRY(switch_case->test()->generate_bytecode(generator)).value(); + auto result = Bytecode::Operand(generator.allocate_register()); + generator.emit(result, test_value, discriminant); next_test_block = &generator.make_block(); - generator.emit( + generator.emit( + result, Bytecode::Label { case_entry_block }, Bytecode::Label { *next_test_block }); } else { @@ -2480,7 +2584,6 @@ Bytecode::CodeGenerationErrorOr> SwitchStatement::ge // Initialize the completion value of the switch statement to empty. We can't do this in the case's basic block directly, // as we must not clobber the possible non-empty completion value of the previous case when falling through. generator.switch_to_basic_block(case_entry_block); - generator.emit(js_undefined()); generator.emit(Bytecode::Label { case_block }); case_blocks.append(case_block); @@ -2491,18 +2594,21 @@ Bytecode::CodeGenerationErrorOr> SwitchStatement::ge if (entry_block_for_default != nullptr) { generator.emit(Bytecode::Label { *entry_block_for_default }); } else { - generator.emit(js_undefined()); generator.emit(Bytecode::Label { end_block }); } auto current_block = case_blocks.begin(); generator.begin_breakable_scope(Bytecode::Label { end_block }, label_set); + auto dst = Bytecode::Operand(generator.allocate_register()); for (auto& switch_case : m_cases) { generator.switch_to_basic_block(*current_block); - for (auto& statement : switch_case->children()) { - FIXME_NEWBC TRY(statement->generate_bytecode(generator)); + auto result = TRY(statement->generate_bytecode(generator)); if (generator.is_current_block_terminated()) break; + if (result.has_value()) + generator.emit(dst, *result); + else + generator.emit(dst, generator.add_constant(js_undefined())); } if (!generator.is_current_block_terminated()) { auto next_block = current_block; @@ -2522,7 +2628,7 @@ Bytecode::CodeGenerationErrorOr> SwitchStatement::ge if (has_lexical_declarations) generator.end_variable_scope(); - return Optional {}; + return dst; } Bytecode::CodeGenerationErrorOr> SuperExpression::generate_bytecode(Bytecode::Generator&, [[maybe_unused]] Optional preferred_dst) const @@ -2534,18 +2640,14 @@ Bytecode::CodeGenerationErrorOr> SuperExpression::ge Bytecode::CodeGenerationErrorOr> ClassDeclaration::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - auto accumulator_backup_reg = generator.allocate_register(); - generator.emit(accumulator_backup_reg); - - FIXME_NEWBC TRY(m_class_expression->generate_bytecode(generator)); - generator.emit_set_variable(*m_class_expression.ptr()->m_name, Bytecode::Op::SetVariable::InitializationMode::Initialize); - - generator.emit(accumulator_backup_reg); + auto value = TRY(m_class_expression->generate_bytecode(generator)).value(); + generator.emit_set_variable(*m_class_expression.ptr()->m_name, value, Bytecode::Op::SetVariable::InitializationMode::Initialize); + // NOTE: ClassDeclaration does not produce a value. return Optional {}; } // 15.7.14 Runtime Semantics: ClassDefinitionEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation -Bytecode::CodeGenerationErrorOr> ClassExpression::generate_bytecode_with_lhs_name(Bytecode::Generator& generator, Optional lhs_name, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> ClassExpression::generate_bytecode_with_lhs_name(Bytecode::Generator& generator, Optional lhs_name, Optional preferred_dst) const { // NOTE: Step 2 is not a part of NewClass instruction because it is assumed to be done before super class expression evaluation generator.emit(); @@ -2556,18 +2658,20 @@ Bytecode::CodeGenerationErrorOr> ClassExpression::ge generator.emit(interned_index, Bytecode::Op::EnvironmentMode::Lexical, true); } + Optional super_class; if (m_super_class) - FIXME_NEWBC TRY(m_super_class->generate_bytecode(generator)); + super_class = TRY(m_super_class->generate_bytecode(generator)).value(); - generator.emit(*this, lhs_name); + auto dst = choose_dst(generator, preferred_dst); + generator.emit(dst, super_class, *this, lhs_name); - return Optional {}; + return dst; } -Bytecode::CodeGenerationErrorOr> ClassExpression::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> ClassExpression::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - return generate_bytecode_with_lhs_name(generator, {}); + return generate_bytecode_with_lhs_name(generator, {}, preferred_dst); } Bytecode::CodeGenerationErrorOr> SpreadExpression::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const @@ -2577,77 +2681,93 @@ Bytecode::CodeGenerationErrorOr> SpreadExpression::g return m_target->generate_bytecode(generator); } -Bytecode::CodeGenerationErrorOr> ThisExpression::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> ThisExpression::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - generator.emit(); - return Optional {}; + auto dst = choose_dst(generator, preferred_dst); + generator.emit(dst); + return dst; } -static void generate_await(Bytecode::Generator& generator, Bytecode::Register received_completion_register, Bytecode::Register received_completion_type_register, Bytecode::Register received_completion_value_register, Bytecode::IdentifierTableIndex type_identifier, Bytecode::IdentifierTableIndex value_identifier) +static Bytecode::Operand generate_await( + Bytecode::Generator& generator, + Bytecode::Operand argument, + Bytecode::Operand received_completion, + Bytecode::Operand received_completion_type, + Bytecode::Operand received_completion_value, + Bytecode::IdentifierTableIndex type_identifier, + Bytecode::IdentifierTableIndex value_identifier) { VERIFY(generator.is_in_async_function()); auto& continuation_block = generator.make_block(); - generator.emit(Bytecode::Label { continuation_block }); + generator.emit(Bytecode::Label { continuation_block }, argument); generator.switch_to_basic_block(continuation_block); - get_received_completion_type_and_value(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier); + // FIXME: It's really magical that we can just assume that the completion value is in register 0. + // It ends up there because we "return" from the Await instruction above via the synthetic + // generator function that actually drives async execution. + generator.emit(received_completion, Bytecode::Operand(Bytecode::Register(0))); + get_received_completion_type_and_value(generator, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier); auto& normal_completion_continuation_block = generator.make_block(); auto& throw_value_block = generator.make_block(); - generator.emit(Value(to_underlying(Completion::Type::Normal))); - generator.emit(received_completion_type_register); - generator.emit( + auto received_completion_type_is_normal = Bytecode::Operand(generator.allocate_register()); + generator.emit( + received_completion_type_is_normal, + received_completion_type, + generator.add_constant(Value(to_underlying(Completion::Type::Normal)))); + generator.emit( + received_completion_type_is_normal, Bytecode::Label { normal_completion_continuation_block }, Bytecode::Label { throw_value_block }); // Simplification: The only abrupt completion we receive from AsyncFunctionDriverWrapper or AsyncGenerator is Type::Throw // So we do not need to account for the Type::Return path generator.switch_to_basic_block(throw_value_block); - generator.emit(received_completion_value_register); generator.perform_needed_unwinds(); - generator.emit(); + generator.emit(received_completion_value); generator.switch_to_basic_block(normal_completion_continuation_block); - generator.emit(received_completion_value_register); + return received_completion_value; } Bytecode::CodeGenerationErrorOr> AwaitExpression::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - FIXME_NEWBC TRY(m_argument->generate_bytecode(generator)); + auto argument = TRY(m_argument->generate_bytecode(generator)).value(); - auto received_completion_register = generator.allocate_register(); - auto received_completion_type_register = generator.allocate_register(); - auto received_completion_value_register = generator.allocate_register(); + auto received_completion = Bytecode::Operand(generator.allocate_register()); + auto received_completion_type = Bytecode::Operand(generator.allocate_register()); + auto received_completion_value = Bytecode::Operand(generator.allocate_register()); + + generator.emit(received_completion, Bytecode::Operand(Bytecode::Register(0))); auto type_identifier = generator.intern_identifier("type"); auto value_identifier = generator.intern_identifier("value"); - generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier); - return Optional {}; + return generate_await(generator, argument, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier); } Bytecode::CodeGenerationErrorOr> WithStatement::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - FIXME_NEWBC TRY(m_object->generate_bytecode(generator)); - generator.emit(); + auto object = TRY(m_object->generate_bytecode(generator)).value(); + generator.emit(object); // EnterObjectEnvironment sets the running execution context's lexical_environment to a new Object Environment. generator.start_boundary(Bytecode::Generator::BlockBoundaryType::LeaveLexicalEnvironment); - generator.emit(js_undefined()); - - FIXME_NEWBC TRY(m_body->generate_bytecode(generator)); + auto body_result = TRY(m_body->generate_bytecode(generator)); + if (!body_result.has_value()) + body_result = generator.add_constant(js_undefined()); generator.end_boundary(Bytecode::Generator::BlockBoundaryType::LeaveLexicalEnvironment); if (!generator.is_current_block_terminated()) generator.emit(); - return Optional {}; + return body_result; } enum class LHSKind { @@ -2666,6 +2786,7 @@ enum class IterationKind { struct ForInOfHeadEvaluationResult { bool is_destructuring { false }; LHSKind lhs_kind { LHSKind::Assignment }; + Optional iterator; }; static Bytecode::CodeGenerationErrorOr for_in_of_head_evaluation(Bytecode::Generator& generator, IterationKind iteration_kind, Variant, NonnullRefPtr> const& lhs, NonnullRefPtr const& rhs) { @@ -2690,8 +2811,8 @@ static Bytecode::CodeGenerationErrorOr for_in_of_he VERIFY(variable->target().has>()); auto identifier = variable->target().get>(); 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); + auto value = TRY(generator.emit_named_evaluation_if_anonymous_function(*variable->init(), identifier_table_ref)).value(); + generator.emit_set_variable(*identifier, value); } } else { auto has_non_local_variables = false; @@ -2729,7 +2850,7 @@ static Bytecode::CodeGenerationErrorOr for_in_of_he } // 3. Let exprRef be the result of evaluating expr. - FIXME_NEWBC TRY(rhs->generate_bytecode(generator)); + auto object = TRY(rhs->generate_bytecode(generator)).value(); // 4. Set the running execution context's LexicalEnvironment to oldEnv. if (entered_lexical_scope) @@ -2738,12 +2859,15 @@ static Bytecode::CodeGenerationErrorOr for_in_of_he // 5. Let exprValue be ? GetValue(exprRef). // NOTE: No need to store this anywhere. + auto iterator = Bytecode::Operand(generator.allocate_register()); + // 6. If iterationKind is enumerate, then if (iteration_kind == IterationKind::Enumerate) { // a. If exprValue is undefined or null, then auto& nullish_block = generator.make_block(); auto& continuation_block = generator.make_block(); generator.emit( + object, Bytecode::Label { nullish_block }, Bytecode::Label { continuation_block }); @@ -2757,7 +2881,7 @@ static Bytecode::CodeGenerationErrorOr for_in_of_he // c. Let iterator be EnumerateObjectProperties(obj). // d. Let nextMethod be ! GetV(iterator, "next"). // e. Return the Iterator Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }. - generator.emit(); + generator.emit(iterator, object); } // 7. Else, else { @@ -2767,25 +2891,24 @@ static Bytecode::CodeGenerationErrorOr for_in_of_he auto iterator_kind = iteration_kind == IterationKind::AsyncIterate ? IteratorHint::Async : IteratorHint::Sync; // d. Return ? GetIterator(exprValue, iteratorKind). - generator.emit(iterator_kind); + generator.emit(iterator, object, iterator_kind); } + result.iterator = iterator; return result; } // 14.7.5.7 ForIn/OfBodyEvaluation ( lhs, stmt, iteratorRecord, iterationKind, lhsKind, labelSet [ , iteratorKind ] ), https://tc39.es/ecma262/#sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset static Bytecode::CodeGenerationErrorOr> for_in_of_body_evaluation(Bytecode::Generator& generator, ASTNode const& node, Variant, NonnullRefPtr> const& lhs, ASTNode const& body, ForInOfHeadEvaluationResult const& head_result, Vector const& label_set, Bytecode::BasicBlock& loop_end, Bytecode::BasicBlock& loop_update, IteratorHint iterator_kind = IteratorHint::Sync, [[maybe_unused]] Optional preferred_dst = {}) { - auto iterator_register = generator.allocate_register(); - generator.emit(iterator_register); - // 1. If iteratorKind is not present, set iteratorKind to sync. // 2. Let oldEnv be the running execution context's LexicalEnvironment. bool has_lexical_binding = false; // 3. Let V be undefined. - // NOTE: We don't need 'V' as the resulting value will naturally flow through via the accumulator register. + auto completion_value = Bytecode::Operand(generator.allocate_register()); + generator.emit(completion_value, generator.add_constant(js_undefined())); // 4. Let destructuring be IsDestructuring of lhs. auto destructuring = head_result.is_destructuring; @@ -2806,39 +2929,41 @@ static Bytecode::CodeGenerationErrorOr> for_in_of_bo generator.begin_continuable_scope(Bytecode::Label { loop_update }, label_set); // a. Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]). - generator.emit(iterator_register); - generator.emit(); + auto next_result = Bytecode::Operand(generator.allocate_register()); + generator.emit(next_result, *head_result.iterator); // b. If iteratorKind is async, set nextResult to ? Await(nextResult). if (iterator_kind == IteratorHint::Async) { - auto received_completion_register = generator.allocate_register(); - auto received_completion_type_register = generator.allocate_register(); - auto received_completion_value_register = generator.allocate_register(); + auto received_completion = Bytecode::Operand(generator.allocate_register()); + auto received_completion_type = Bytecode::Operand(generator.allocate_register()); + auto received_completion_value = Bytecode::Operand(generator.allocate_register()); auto type_identifier = generator.intern_identifier("type"); auto value_identifier = generator.intern_identifier("value"); - generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier); + generator.emit(received_completion, Bytecode::Operand(Bytecode::Register(0))); + auto new_result = generate_await(generator, next_result, received_completion, received_completion_type, received_completion_value, type_identifier, value_identifier); + generator.emit(next_result, new_result); } // c. If Type(nextResult) is not Object, throw a TypeError exception. - generator.emit(); + generator.emit(next_result); // d. Let done be ? IteratorComplete(nextResult). - auto iterator_result_register = generator.allocate_register(); - generator.emit(iterator_result_register); - generator.emit_iterator_complete(); + auto done = Bytecode::Operand(generator.allocate_register()); + generator.emit_iterator_complete(done, next_result); // e. If done is true, return V. auto& loop_continue = generator.make_block(); - generator.emit( + generator.emit( + done, Bytecode::Label { loop_end }, Bytecode::Label { loop_continue }); generator.switch_to_basic_block(loop_continue); // f. Let nextValue be ? IteratorValue(nextResult). - generator.emit(iterator_result_register); - generator.emit_iterator_value(); + auto next_value = Bytecode::Operand(generator.allocate_register()); + generator.emit_iterator_value(next_value, next_result); // g. If lhsKind is either assignment or varBinding, then if (head_result.lhs_kind != LHSKind::LexicalBinding) { @@ -2849,15 +2974,13 @@ static Bytecode::CodeGenerationErrorOr> for_in_of_bo if (head_result.lhs_kind == LHSKind::VarBinding) { auto& declaration = static_cast(*lhs.get>()); VERIFY(declaration.declarations().size() == 1); - FIXME_NEWBC TRY(assign_accumulator_to_variable_declarator(generator, declaration.declarations().first(), declaration)); + TRY(assign_value_to_variable_declarator(generator, declaration.declarations().first(), declaration, next_value)); } else { if (auto ptr = lhs.get_pointer>()) { - FIXME_NEWBC TRY(generator.emit_store_to_reference(**ptr)); + TRY(generator.emit_store_to_reference(**ptr, next_value)); } else { auto& binding_pattern = lhs.get>(); - auto value_register = generator.allocate_register(); - generator.emit(value_register); - FIXME_NEWBC TRY(generate_binding_pattern_bytecode(generator, *binding_pattern, Bytecode::Op::SetVariable::InitializationMode::Set, value_register, false)); + TRY(generate_binding_pattern_bytecode(generator, *binding_pattern, Bytecode::Op::SetVariable::InitializationMode::Set, next_value, false)); } } } @@ -2912,10 +3035,8 @@ static Bytecode::CodeGenerationErrorOr> for_in_of_bo auto lhs_name = variable_declaration.declarations().first()->target().get>(); // 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. - if (lhs_name->is_local()) - generator.emit(lhs_name->local_variable_index()); - else - generator.emit_set_variable(*lhs_name, Bytecode::Op::SetVariable::InitializationMode::Initialize, Bytecode::Op::EnvironmentMode::Lexical); + + generator.emit_set_variable(*lhs_name, next_value, Bytecode::Op::SetVariable::InitializationMode::Initialize, Bytecode::Op::EnvironmentMode::Lexical); } } // i. If destructuring is false, then @@ -2944,10 +3065,12 @@ static Bytecode::CodeGenerationErrorOr> for_in_of_bo auto& declaration = static_cast(*lhs.get>()); VERIFY(declaration.declarations().size() == 1); auto& binding_pattern = declaration.declarations().first()->target().get>(); - - auto value_register = generator.allocate_register(); - generator.emit(value_register); - FIXME_NEWBC TRY(generate_binding_pattern_bytecode(generator, *binding_pattern, head_result.lhs_kind == LHSKind::VarBinding ? Bytecode::Op::SetVariable::InitializationMode::Set : Bytecode::Op::SetVariable::InitializationMode::Initialize, value_register, false)); + (void)TRY(generate_binding_pattern_bytecode( + generator, + *binding_pattern, + head_result.lhs_kind == LHSKind::VarBinding ? Bytecode::Op::SetVariable::InitializationMode::Set : Bytecode::Op::SetVariable::InitializationMode::Initialize, + next_value, + false)); } else { return Bytecode::CodeGenerationError { &node, @@ -2966,14 +3089,8 @@ static Bytecode::CodeGenerationErrorOr> for_in_of_bo // 1. Assert: iterationKind is iterate. // 2. Return ? IteratorClose(iteratorRecord, status). - generator.emit(js_undefined()); - // l. Let result be the result of evaluating stmt. - FIXME_NEWBC TRY(body.generate_bytecode(generator)); - - auto result_register = generator.allocate_register(); - if (!generator.is_current_block_terminated()) - generator.emit(result_register); + auto result = TRY(body.generate_bytecode(generator)); // m. Set the running execution context's LexicalEnvironment to oldEnv. if (has_lexical_binding) @@ -2993,12 +3110,15 @@ static Bytecode::CodeGenerationErrorOr> for_in_of_bo // o. If result.[[Value]] is not empty, set V to result.[[Value]]. // The body can contain an unconditional block terminator (e.g. return, throw), so we have to check for that before generating the Jump. - if (!generator.is_current_block_terminated()) + if (!generator.is_current_block_terminated()) { + if (result.has_value()) + generator.emit(completion_value, *result); + generator.emit(Bytecode::Label { loop_update }); + } generator.switch_to_basic_block(loop_end); - generator.emit(result_register); - return Optional {}; + return completion_value; } Bytecode::CodeGenerationErrorOr> ForInStatement::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const @@ -3015,8 +3135,6 @@ Bytecode::CodeGenerationErrorOr> ForInStatement::gen generator.begin_breakable_scope(Bytecode::Label { loop_end }, label_set); auto head_result = TRY(for_in_of_head_evaluation(generator, IterationKind::Enumerate, m_lhs, m_rhs)); - - // Now perform the rest of ForInOfLoopEvaluation, given that the accumulator holds the iterator we're supposed to iterate over. return for_in_of_body_evaluation(generator, *this, m_lhs, body(), head_result, label_set, loop_end, loop_update); } @@ -3033,8 +3151,6 @@ Bytecode::CodeGenerationErrorOr> ForOfStatement::gen generator.begin_breakable_scope(Bytecode::Label { loop_end }, label_set); auto head_result = TRY(for_in_of_head_evaluation(generator, IterationKind::Iterate, m_lhs, m_rhs)); - - // Now perform the rest of ForInOfLoopEvaluation, given that the accumulator holds the iterator we're supposed to iterate over. return for_in_of_body_evaluation(generator, *this, m_lhs, body(), head_result, label_set, loop_end, loop_update); } @@ -3051,53 +3167,57 @@ Bytecode::CodeGenerationErrorOr> ForAwaitOfStatement generator.begin_breakable_scope(Bytecode::Label { loop_end }, label_set); auto head_result = TRY(for_in_of_head_evaluation(generator, IterationKind::AsyncIterate, m_lhs, m_rhs)); - - // Now perform the rest of ForInOfLoopEvaluation, given that the accumulator holds the iterator we're supposed to iterate over. return for_in_of_body_evaluation(generator, *this, m_lhs, m_body, head_result, label_set, loop_end, loop_update, IteratorHint::Async); } // 13.3.12.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-meta-properties-runtime-semantics-evaluation -Bytecode::CodeGenerationErrorOr> MetaProperty::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> MetaProperty::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); // NewTarget : new . target if (m_type == MetaProperty::Type::NewTarget) { // 1. Return GetNewTarget(). - generator.emit(); - return Optional {}; + auto dst = choose_dst(generator, preferred_dst); + generator.emit(dst); + return dst; } // ImportMeta : import . meta if (m_type == MetaProperty::Type::ImportMeta) { - generator.emit(); - return Optional {}; + auto dst = choose_dst(generator, preferred_dst); + generator.emit(dst); + return dst; } VERIFY_NOT_REACHED(); } -Bytecode::CodeGenerationErrorOr> ClassFieldInitializerStatement::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> ClassFieldInitializerStatement::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - TRY(generator.emit_named_evaluation_if_anonymous_function(*m_expression, generator.intern_identifier(m_class_field_identifier_name))); + auto value = TRY(generator.emit_named_evaluation_if_anonymous_function(*m_expression, generator.intern_identifier(m_class_field_identifier_name), preferred_dst)); generator.perform_needed_unwinds(); - generator.emit(); - return Optional {}; + generator.emit(value); + return value; } -static Bytecode::CodeGenerationErrorOr> generate_optional_chain(Bytecode::Generator& generator, OptionalChain const& optional_chain, Bytecode::Register current_value_register, Bytecode::Register current_base_register, [[maybe_unused]] Optional preferred_dst) +static Bytecode::CodeGenerationErrorOr generate_optional_chain(Bytecode::Generator& generator, OptionalChain const& optional_chain, Bytecode::Operand current_value, Bytecode::Operand current_base, [[maybe_unused]] Optional preferred_dst) { + Optional new_current_value; if (is(optional_chain.base())) { auto& member_expression = static_cast(optional_chain.base()); - FIXME_NEWBC TRY(get_base_and_value_from_member_expression(generator, member_expression, current_base_register)); + auto base_and_value = TRY(get_base_and_value_from_member_expression(generator, member_expression)); + new_current_value = base_and_value.value; + generator.emit(current_base, base_and_value.base); } else if (is(optional_chain.base())) { auto& sub_optional_chain = static_cast(optional_chain.base()); - FIXME_NEWBC TRY(generate_optional_chain(generator, sub_optional_chain, current_value_register, current_base_register)); + TRY(generate_optional_chain(generator, sub_optional_chain, current_value, current_base)); + new_current_value = current_value; } else { - FIXME_NEWBC TRY(optional_chain.base().generate_bytecode(generator)); + new_current_value = TRY(optional_chain.base().generate_bytecode(generator)).value(); } - generator.emit(current_value_register); + generator.emit(current_value, *new_current_value); auto& load_undefined_and_jump_to_end_block = generator.make_block(); auto& end_block = generator.make_block(); @@ -3107,82 +3227,72 @@ static Bytecode::CodeGenerationErrorOr> generate_opt if (is_optional) { auto& not_nullish_block = generator.make_block(); generator.emit( + current_value, Bytecode::Label { load_undefined_and_jump_to_end_block }, Bytecode::Label { not_nullish_block }); generator.switch_to_basic_block(not_nullish_block); } - FIXME_NEWBC TRY(reference.visit( - [&](OptionalChain::Call const& call) -> Bytecode::CodeGenerationErrorOr> { - FIXME_NEWBC TRY(arguments_to_array_for_call(generator, call.arguments)); - generator.emit(Bytecode::Op::CallType::Call, current_value_register, current_base_register); - - generator.emit(current_value_register); - - generator.emit(js_undefined()); - generator.emit(current_base_register); - - generator.emit(current_value_register); - return Optional {}; + TRY(reference.visit( + [&](OptionalChain::Call const& call) -> Bytecode::CodeGenerationErrorOr { + auto arguments = TRY(arguments_to_array_for_call(generator, call.arguments)).value(); + generator.emit(Bytecode::Op::CallType::Call, current_value, current_value, current_base, arguments); + generator.emit(current_base, generator.add_constant(js_undefined())); + return {}; }, - [&](OptionalChain::ComputedReference const& ref) -> Bytecode::CodeGenerationErrorOr> { - generator.emit(current_base_register); - FIXME_NEWBC TRY(ref.expression->generate_bytecode(generator)); - generator.emit(current_base_register); - generator.emit(current_value_register); - return Optional {}; + [&](OptionalChain::ComputedReference const& ref) -> Bytecode::CodeGenerationErrorOr { + generator.emit(current_base, current_value); + auto property = TRY(ref.expression->generate_bytecode(generator)).value(); + generator.emit(current_value, current_value, property); + return {}; }, - [&](OptionalChain::MemberReference const& ref) -> Bytecode::CodeGenerationErrorOr> { - generator.emit(current_base_register); - generator.emit_get_by_id(generator.intern_identifier(ref.identifier->string())); - generator.emit(current_value_register); - return Optional {}; + [&](OptionalChain::MemberReference const& ref) -> Bytecode::CodeGenerationErrorOr { + generator.emit(current_base, current_value); + generator.emit_get_by_id(current_value, current_value, generator.intern_identifier(ref.identifier->string())); + return {}; }, - [&](OptionalChain::PrivateMemberReference const& ref) -> Bytecode::CodeGenerationErrorOr> { - generator.emit(current_base_register); - generator.emit(generator.intern_identifier(ref.private_identifier->string())); - generator.emit(current_value_register); - return Optional {}; + [&](OptionalChain::PrivateMemberReference const& ref) -> Bytecode::CodeGenerationErrorOr { + generator.emit(current_base, current_value); + generator.emit(current_value, current_value, generator.intern_identifier(ref.private_identifier->string())); + + return {}; })); } generator.emit(Bytecode::Label { end_block }); generator.switch_to_basic_block(load_undefined_and_jump_to_end_block); - generator.emit(js_undefined()); + generator.emit(current_value, generator.add_constant(js_undefined())); generator.emit(Bytecode::Label { end_block }); generator.switch_to_basic_block(end_block); - return Optional {}; + return {}; } Bytecode::CodeGenerationErrorOr> OptionalChain::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - auto current_base_register = generator.allocate_register(); - auto current_value_register = generator.allocate_register(); - generator.emit(js_undefined()); - generator.emit(current_base_register); - return generate_optional_chain(generator, *this, current_value_register, current_base_register); + auto current_base = Bytecode::Operand(generator.allocate_register()); + auto current_value = choose_dst(generator, preferred_dst); + generator.emit(current_base, generator.add_constant(js_undefined())); + TRY(generate_optional_chain(generator, *this, current_value, current_base)); + return current_value; } -Bytecode::CodeGenerationErrorOr> ImportCall::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const +Bytecode::CodeGenerationErrorOr> ImportCall::generate_bytecode(Bytecode::Generator& generator, Optional preferred_dst) const { Bytecode::Generator::SourceLocationScope scope(generator, *this); - FIXME_NEWBC TRY(m_specifier->generate_bytecode(generator)); - auto specifier_reg = generator.allocate_register(); - generator.emit(specifier_reg); + auto specifier = TRY(m_specifier->generate_bytecode(generator)).value(); + Optional options; if (m_options) { - FIXME_NEWBC TRY(m_options->generate_bytecode(generator)); + options = TRY(m_options->generate_bytecode(generator)).value(); } else { - generator.emit(js_undefined()); + options = generator.add_constant(js_undefined()); } - auto options_reg = generator.allocate_register(); - generator.emit(options_reg); - - generator.emit(specifier_reg, options_reg); - return Optional {}; + auto dst = choose_dst(generator, preferred_dst); + generator.emit(dst, specifier, *options); + return dst; } Bytecode::CodeGenerationErrorOr> ExportStatement::generate_bytecode(Bytecode::Generator& generator, [[maybe_unused]] Optional preferred_dst) const @@ -3202,19 +3312,28 @@ Bytecode::CodeGenerationErrorOr> ExportStatement::ge } if (is(*m_statement)) { - TRY(generator.emit_named_evaluation_if_anonymous_function(static_cast(*m_statement), generator.intern_identifier("default"sv))); + auto value = TRY(generator.emit_named_evaluation_if_anonymous_function(static_cast(*m_statement), generator.intern_identifier("default"sv))).value(); - if (!static_cast(*m_statement).has_name()) - generator.emit(generator.intern_identifier(ExportStatement::local_name_for_default), generator.next_environment_variable_cache(), Bytecode::Op::SetVariable::InitializationMode::Initialize); + if (!static_cast(*m_statement).has_name()) { + generator.emit( + generator.intern_identifier(ExportStatement::local_name_for_default), + value, + generator.next_environment_variable_cache(), + Bytecode::Op::SetVariable::InitializationMode::Initialize); + } - return Optional {}; + return value; } // ExportDeclaration : export default AssignmentExpression ; VERIFY(is(*m_statement)); - TRY(generator.emit_named_evaluation_if_anonymous_function(static_cast(*m_statement), generator.intern_identifier("default"sv))); - generator.emit(generator.intern_identifier(ExportStatement::local_name_for_default), generator.next_environment_variable_cache(), Bytecode::Op::SetVariable::InitializationMode::Initialize); - return Optional {}; + auto value = TRY(generator.emit_named_evaluation_if_anonymous_function(static_cast(*m_statement), generator.intern_identifier("default"sv))).value(); + generator.emit( + generator.intern_identifier(ExportStatement::local_name_for_default), + value, + generator.next_environment_variable_cache(), + Bytecode::Op::SetVariable::InitializationMode::Initialize); + return value; } Bytecode::CodeGenerationErrorOr> ImportStatement::generate_bytecode(Bytecode::Generator&, [[maybe_unused]] Optional preferred_dst) const diff --git a/Userland/Libraries/LibJS/Bytecode/CommonImplementations.h b/Userland/Libraries/LibJS/Bytecode/CommonImplementations.h index dc72e4d99a..af5210b4a7 100644 --- a/Userland/Libraries/LibJS/Bytecode/CommonImplementations.h +++ b/Userland/Libraries/LibJS/Bytecode/CommonImplementations.h @@ -367,7 +367,7 @@ inline ThrowCompletionOr set_variable( return {}; } -inline Value new_function(VM& vm, FunctionExpression const& function_node, Optional const& lhs_name, Optional const& home_object) +inline Value new_function(VM& vm, FunctionExpression const& function_node, Optional const& lhs_name, Optional const& home_object) { Value value; @@ -381,7 +381,7 @@ inline Value new_function(VM& vm, FunctionExpression const& function_node, Optio } if (home_object.has_value()) { - auto home_object_value = vm.bytecode_interpreter().reg(home_object.value()); + auto home_object_value = vm.bytecode_interpreter().get(home_object.value()); static_cast(value.as_function()).set_home_object(&home_object_value.as_object()); } diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.cpp b/Userland/Libraries/LibJS/Bytecode/Generator.cpp index 15ca38434c..813a716a43 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Generator.cpp @@ -1,11 +1,9 @@ /* - * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2021-2024, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ -#define FIXME_NEWBC (void) - #include #include #include @@ -24,29 +22,42 @@ Generator::Generator() { } -CodeGenerationErrorOr> Generator::generate(VM& vm, ASTNode const& node, FunctionKind enclosing_function_kind) +CodeGenerationErrorOr> Generator::generate(VM& vm, ASTNode const& node, ReadonlySpan parameters, FunctionKind enclosing_function_kind) { Generator generator; + + for (auto const& parameter : parameters) { + if (auto const* identifier = parameter.binding.get_pointer>(); + identifier && (*identifier)->is_local()) { + generator.set_local_initialized((*identifier)->local_variable_index()); + } + } + generator.switch_to_basic_block(generator.make_block()); SourceLocationScope scope(generator, node); generator.m_enclosing_function_kind = enclosing_function_kind; if (generator.is_in_generator_or_async_function()) { // Immediately yield with no value. auto& start_block = generator.make_block(); - generator.emit(Label { start_block }); + generator.emit(Label { start_block }, generator.add_constant(js_undefined())); generator.switch_to_basic_block(start_block); // NOTE: This doesn't have to handle received throw/return completions, as GeneratorObject::resume_abrupt // will not enter the generator from the SuspendedStart state and immediately completes the generator. } - FIXME_NEWBC TRY(node.generate_bytecode(generator)); + + auto last_value = TRY(node.generate_bytecode(generator)); + + if (!generator.current_block().is_terminated() && last_value.has_value()) { + generator.emit(last_value.value()); + } + if (generator.is_in_generator_or_async_function()) { // Terminate all unterminated blocks with yield return for (auto& block : generator.m_root_basic_blocks) { if (block->is_terminated()) continue; generator.switch_to_basic_block(*block); - generator.emit(js_undefined()); - generator.emit(nullptr); + generator.emit(nullptr, generator.add_constant(js_undefined())); } } @@ -169,26 +180,23 @@ void Generator::end_breakable_scope() end_boundary(BlockBoundaryType::Break); } -CodeGenerationErrorOr Generator::emit_super_reference(MemberExpression const& expression) +CodeGenerationErrorOr Generator::emit_super_reference(MemberExpression const& expression) { VERIFY(is(expression.object())); // https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation // 1. Let env be GetThisEnvironment(). // 2. Let actualThis be ? env.GetThisBinding(). - auto actual_this_register = allocate_register(); - emit(); - emit(actual_this_register); + auto actual_this = Operand(allocate_register()); + emit(actual_this); - Optional computed_property_value_register; + Optional computed_property_value; if (expression.is_computed()) { // SuperProperty : super [ Expression ] // 3. Let propertyNameReference be ? Evaluation of Expression. // 4. Let propertyNameValue be ? GetValue(propertyNameReference). - FIXME_NEWBC TRY(expression.property().generate_bytecode(*this)); - computed_property_value_register = allocate_register(); - emit(*computed_property_value_register); + computed_property_value = TRY(expression.property().generate_bytecode(*this)).value(); } // 5/7. Return ? MakeSuperPropertyReference(actualThis, propertyKey, strict). @@ -197,135 +205,128 @@ CodeGenerationErrorOr Generator::emit_super_refer // 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); + auto base_value = Operand(allocate_register()); + emit(base_value); // 4. Return the Reference Record { [[Base]]: baseValue, [[ReferencedName]]: propertyKey, [[Strict]]: strict, [[ThisValue]]: actualThis }. - return ReferenceRegisters { - .base = super_base_register, - .referenced_name = move(computed_property_value_register), - .this_value = actual_this_register, + return ReferenceOperands { + .base = base_value, + .referenced_name = computed_property_value, + .this_value = actual_this, }; } -CodeGenerationErrorOr> Generator::emit_load_from_reference(JS::ASTNode const& node, CollectRegisters collect_registers) +CodeGenerationErrorOr Generator::emit_load_from_reference(JS::ASTNode const& node, Optional preferred_dst) { if (is(node)) { auto& identifier = static_cast(node); - FIXME_NEWBC TRY(identifier.generate_bytecode(*this)); - return Optional {}; + auto loaded_value = TRY(identifier.generate_bytecode(*this, preferred_dst)).value(); + return ReferenceOperands { + .loaded_value = loaded_value, + }; } - if (is(node)) { - auto& expression = static_cast(node); + if (!is(node)) { + return CodeGenerationError { + &node, + "Unimplemented/invalid node used as a reference"sv + }; + } + auto& expression = static_cast(node); - // https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation - if (is(expression.object())) { - auto super_reference = TRY(emit_super_reference(expression)); + // https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation + if (is(expression.object())) { + auto super_reference = TRY(emit_super_reference(expression)); + auto dst = preferred_dst.has_value() ? preferred_dst.value() : Operand(allocate_register()); - if (super_reference.referenced_name.has_value()) { - // 5. Let propertyKey be ? ToPropertyKey(propertyNameValue). - // FIXME: This does ToPropertyKey out of order, which is observable by Symbol.toPrimitive! - emit(*super_reference.referenced_name); - emit(super_reference.base, super_reference.this_value); - } else { - // 3. Let propertyKey be StringValue of IdentifierName. - auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); - emit_get_by_id_with_this(identifier_table_ref, super_reference.this_value); - } - - return super_reference; + if (super_reference.referenced_name.has_value()) { + // 5. Let propertyKey be ? ToPropertyKey(propertyNameValue). + // FIXME: This does ToPropertyKey out of order, which is observable by Symbol.toPrimitive! + emit(dst, *super_reference.base, *super_reference.referenced_name, *super_reference.this_value); } else { - FIXME_NEWBC TRY(expression.object().generate_bytecode(*this)); - if (expression.is_computed()) { - auto object_reg = allocate_register(); - emit(object_reg); - - FIXME_NEWBC TRY(expression.property().generate_bytecode(*this)); - Optional property_reg {}; - if (collect_registers == CollectRegisters::Yes) { - property_reg = allocate_register(); - emit(property_reg.value()); - } - - emit(object_reg); - if (collect_registers == CollectRegisters::Yes) - return ReferenceRegisters { - .base = object_reg, - .referenced_name = property_reg.value(), - .this_value = object_reg, - }; - return Optional {}; - } else if (expression.property().is_identifier()) { - auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); - emit_get_by_id(identifier_table_ref); - } else if (expression.property().is_private_identifier()) { - auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); - emit(identifier_table_ref); - } else { - return CodeGenerationError { - &expression, - "Unimplemented non-computed member expression"sv - }; - } + // 3. Let propertyKey be StringValue of IdentifierName. + auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); + emit_get_by_id_with_this(dst, *super_reference.base, identifier_table_ref, *super_reference.this_value); } - return Optional {}; + + super_reference.loaded_value = dst; + return super_reference; + } + auto base = TRY(expression.object().generate_bytecode(*this)).value(); + if (expression.is_computed()) { + auto property = TRY(expression.property().generate_bytecode(*this)).value(); + auto dst = preferred_dst.has_value() ? preferred_dst.value() : Operand(allocate_register()); + emit(dst, base, property); + return ReferenceOperands { + .base = base, + .referenced_name = property, + .this_value = base, + .loaded_value = dst, + }; + } + if (expression.property().is_identifier()) { + auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); + auto dst = preferred_dst.has_value() ? preferred_dst.value() : Operand(allocate_register()); + emit_get_by_id(dst, base, identifier_table_ref); + return ReferenceOperands { + .base = base, + .referenced_identifier = identifier_table_ref, + .this_value = base, + .loaded_value = dst, + }; + } + if (expression.property().is_private_identifier()) { + auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); + auto dst = preferred_dst.has_value() ? preferred_dst.value() : Operand(allocate_register()); + emit(dst, base, identifier_table_ref); + return ReferenceOperands { + .base = base, + .referenced_private_identifier = identifier_table_ref, + .this_value = base, + .loaded_value = dst, + }; } return CodeGenerationError { - &node, - "Unimplemented/invalid node used a reference"sv + &expression, + "Unimplemented non-computed member expression"sv }; } -CodeGenerationErrorOr> Generator::emit_store_to_reference(JS::ASTNode const& node) +CodeGenerationErrorOr Generator::emit_store_to_reference(JS::ASTNode const& node, Operand value) { if (is(node)) { auto& identifier = static_cast(node); - emit_set_variable(identifier); - return Optional {}; + emit_set_variable(identifier, value); + return {}; } if (is(node)) { - // NOTE: The value is in the accumulator, so we have to store that away first. - auto value_reg = allocate_register(); - emit(value_reg); - auto& expression = static_cast(node); // https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation if (is(expression.object())) { auto super_reference = TRY(emit_super_reference(expression)); - emit(value_reg); // 4. Return the Reference Record { [[Base]]: baseValue, [[ReferencedName]]: propertyKey, [[Strict]]: strict, [[ThisValue]]: actualThis }. if (super_reference.referenced_name.has_value()) { // 5. Let propertyKey be ? ToPropertyKey(propertyNameValue). // FIXME: This does ToPropertyKey out of order, which is observable by Symbol.toPrimitive! - emit(super_reference.base, *super_reference.referenced_name, super_reference.this_value); + emit(*super_reference.base, *super_reference.referenced_name, *super_reference.this_value, value); } else { // 3. Let propertyKey be StringValue of IdentifierName. auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); - emit(super_reference.base, super_reference.this_value, identifier_table_ref, Bytecode::Op::PropertyKind::KeyValue, next_property_lookup_cache()); + emit(*super_reference.base, *super_reference.this_value, identifier_table_ref, value, Bytecode::Op::PropertyKind::KeyValue, next_property_lookup_cache()); } } else { - FIXME_NEWBC TRY(expression.object().generate_bytecode(*this)); - - auto object_reg = allocate_register(); - emit(object_reg); + auto object = TRY(expression.object().generate_bytecode(*this)).value(); if (expression.is_computed()) { - FIXME_NEWBC TRY(expression.property().generate_bytecode(*this)); - auto property_reg = allocate_register(); - emit(property_reg); - emit(value_reg); - emit(object_reg, property_reg); + auto property = TRY(expression.property().generate_bytecode(*this)).value(); + emit(object, property, value); } 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, Bytecode::Op::PropertyKind::KeyValue, next_property_lookup_cache()); + emit(object, identifier_table_ref, value, Bytecode::Op::PropertyKind::KeyValue, next_property_lookup_cache()); } 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); + emit(object, identifier_table_ref, value); } else { return CodeGenerationError { &expression, @@ -334,7 +335,7 @@ CodeGenerationErrorOr> Generator::emit_store_to_reference(JS:: } } - return Optional {}; + return {}; } return CodeGenerationError { @@ -343,24 +344,36 @@ CodeGenerationErrorOr> Generator::emit_store_to_reference(JS:: }; } -CodeGenerationErrorOr> Generator::emit_store_to_reference(ReferenceRegisters const& reference_registers) +CodeGenerationErrorOr Generator::emit_store_to_reference(ReferenceOperands const& reference, Operand value) { - if (reference_registers.base == reference_registers.this_value) - emit(reference_registers.base, reference_registers.referenced_name.value()); + if (reference.referenced_private_identifier.has_value()) { + emit(*reference.base, *reference.referenced_private_identifier, value); + return {}; + } + if (reference.referenced_identifier.has_value()) { + if (reference.base == reference.this_value) + emit(*reference.base, *reference.referenced_identifier, value, Bytecode::Op::PropertyKind::KeyValue, next_property_lookup_cache()); + else + emit(*reference.base, *reference.this_value, *reference.referenced_identifier, value, Bytecode::Op::PropertyKind::KeyValue, next_property_lookup_cache()); + return {}; + } + if (reference.base == reference.this_value) + emit(*reference.base, *reference.referenced_name, value); else - emit(reference_registers.base, reference_registers.referenced_name.value(), reference_registers.this_value); - return Optional {}; + emit(*reference.base, *reference.referenced_name, *reference.this_value, value); + return {}; } CodeGenerationErrorOr> Generator::emit_delete_reference(JS::ASTNode const& node) { if (is(node)) { auto& identifier = static_cast(node); - if (identifier.is_local()) - emit(Value(false)); - else - emit(intern_identifier(identifier.string())); - return Optional {}; + if (identifier.is_local()) { + return add_constant(Value(false)); + } + auto dst = Operand(allocate_register()); + emit(dst, intern_identifier(identifier.string())); + return dst; } if (is(node)) { @@ -370,27 +383,26 @@ CodeGenerationErrorOr> Generator::emit_delete_reference(JS::AS if (is(expression.object())) { auto super_reference = TRY(emit_super_reference(expression)); + auto dst = Operand(allocate_register()); if (super_reference.referenced_name.has_value()) { - emit(super_reference.this_value, *super_reference.referenced_name); + emit(dst, *super_reference.base, *super_reference.this_value, *super_reference.referenced_name); } else { auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); - emit(super_reference.this_value, identifier_table_ref); + emit(dst, *super_reference.base, *super_reference.this_value, identifier_table_ref); } return Optional {}; } - FIXME_NEWBC TRY(expression.object().generate_bytecode(*this)); + auto object = TRY(expression.object().generate_bytecode(*this)).value(); + auto dst = Operand(allocate_register()); if (expression.is_computed()) { - auto object_reg = allocate_register(); - emit(object_reg); - - FIXME_NEWBC TRY(expression.property().generate_bytecode(*this)); - emit(object_reg); + auto property = TRY(expression.property().generate_bytecode(*this)).value(); + emit(dst, object, property); } else if (expression.property().is_identifier()) { auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); - emit(identifier_table_ref); + emit(dst, object, identifier_table_ref); } else { // NOTE: Trying to delete a private field generates a SyntaxError in the parser. return CodeGenerationError { @@ -398,7 +410,7 @@ CodeGenerationErrorOr> Generator::emit_delete_reference(JS::AS "Unimplemented non-computed member expression"sv }; } - return Optional {}; + return dst; } // Though this will have no deletion effect, we still have to evaluate the node as it can have side effects. @@ -407,21 +419,23 @@ CodeGenerationErrorOr> Generator::emit_delete_reference(JS::AS // 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). - FIXME_NEWBC TRY(node.generate_bytecode(*this)); + (void)TRY(node.generate_bytecode(*this)); // 3. If ref is not a Reference Record, return true. - emit(Value(true)); - // NOTE: The rest of the steps are handled by Delete{Variable,ByValue,Id}. - return Optional {}; + return add_constant(Value(true)); } -void Generator::emit_set_variable(JS::Identifier const& identifier, Bytecode::Op::SetVariable::InitializationMode initialization_mode, Bytecode::Op::EnvironmentMode mode) +void Generator::emit_set_variable(JS::Identifier const& identifier, Operand value, Bytecode::Op::SetVariable::InitializationMode initialization_mode, Bytecode::Op::EnvironmentMode mode) { if (identifier.is_local()) { - emit(identifier.local_variable_index()); + if (value.is_local() && value.index() == identifier.local_variable_index()) { + // Moving a local to itself is a no-op. + return; + } + emit(identifier.local_variable_index(), value); } else { - emit(intern_identifier(identifier.string()), next_environment_variable_cache(), initialization_mode, mode); + emit(intern_identifier(identifier.string()), value, next_environment_variable_cache(), initialization_mode, mode); } } @@ -540,9 +554,9 @@ void Generator::generate_continue(DeprecatedFlyString const& continue_label) generate_labelled_jump(JumpType::Continue, continue_label); } -void Generator::push_home_object(Register register_) +void Generator::push_home_object(Operand object) { - m_home_objects.append(register_); + m_home_objects.append(object); } void Generator::pop_home_object() @@ -550,54 +564,62 @@ void Generator::pop_home_object() m_home_objects.take_last(); } -void Generator::emit_new_function(FunctionExpression const& function_node, Optional lhs_name) +void Generator::emit_new_function(Operand dst, FunctionExpression const& function_node, Optional lhs_name) { - if (m_home_objects.is_empty()) - emit(function_node, lhs_name); - else - emit(function_node, lhs_name, m_home_objects.last()); + if (m_home_objects.is_empty()) { + emit(dst, function_node, lhs_name); + } else { + emit(dst, function_node, lhs_name, m_home_objects.last()); + } } -CodeGenerationErrorOr Generator::emit_named_evaluation_if_anonymous_function(Expression const& expression, Optional lhs_name) +CodeGenerationErrorOr> Generator::emit_named_evaluation_if_anonymous_function(Expression const& expression, Optional lhs_name, Optional preferred_dst) { if (is(expression)) { auto const& function_expression = static_cast(expression); if (!function_expression.has_name()) { - FIXME_NEWBC TRY(function_expression.generate_bytecode_with_lhs_name(*this, move(lhs_name))); - return {}; + return TRY(function_expression.generate_bytecode_with_lhs_name(*this, move(lhs_name), preferred_dst)).value(); } } if (is(expression)) { auto const& class_expression = static_cast(expression); if (!class_expression.has_name()) { - FIXME_NEWBC TRY(class_expression.generate_bytecode_with_lhs_name(*this, move(lhs_name))); - return {}; + return TRY(class_expression.generate_bytecode_with_lhs_name(*this, move(lhs_name), preferred_dst)).value(); } } - FIXME_NEWBC TRY(expression.generate_bytecode(*this)); - return {}; + return expression.generate_bytecode(*this, preferred_dst); } -void Generator::emit_get_by_id(IdentifierTableIndex id) +void Generator::emit_get_by_id(Operand dst, Operand base, IdentifierTableIndex id) { - emit(id, m_next_property_lookup_cache++); + emit(dst, base, id, m_next_property_lookup_cache++); } -void Generator::emit_get_by_id_with_this(IdentifierTableIndex id, Register this_reg) +void Generator::emit_get_by_id_with_this(Operand dst, Operand base, IdentifierTableIndex id, Operand this_value) { - emit(id, this_reg, m_next_property_lookup_cache++); + emit(dst, base, id, this_value, m_next_property_lookup_cache++); } -void Generator::emit_iterator_value() +void Generator::emit_iterator_value(Operand dst, Operand result) { - emit_get_by_id(intern_identifier("value"sv)); + emit_get_by_id(dst, result, intern_identifier("value"sv)); } -void Generator::emit_iterator_complete() +void Generator::emit_iterator_complete(Operand dst, Operand result) { - emit_get_by_id(intern_identifier("done"sv)); + emit_get_by_id(dst, result, intern_identifier("done"sv)); +} + +bool Generator::is_local_initialized(u32 local_index) const +{ + return m_initialized_locals.find(local_index) != m_initialized_locals.end(); +} + +void Generator::set_local_initialized(u32 local_index) +{ + m_initialized_locals.set(local_index); } } diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.h b/Userland/Libraries/LibJS/Bytecode/Generator.h index 235ae3d79f..5a2ae26f1a 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.h +++ b/Userland/Libraries/LibJS/Bytecode/Generator.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2021-2024, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ @@ -30,10 +30,13 @@ public: Function, Block, }; - static CodeGenerationErrorOr> generate(VM&, ASTNode const&, FunctionKind = FunctionKind::Normal); + static CodeGenerationErrorOr> generate(VM&, ASTNode const&, ReadonlySpan parameters, FunctionKind = FunctionKind::Normal); Register allocate_register(); + void set_local_initialized(u32 local_index); + [[nodiscard]] bool is_local_initialized(u32 local_index) const; + class SourceLocationScope { public: SourceLocationScope(Generator&, ASTNode const& node); @@ -77,11 +80,11 @@ public: } template - void emit_with_extra_register_slots(size_t extra_register_slots, Args&&... args) + void emit_with_extra_operand_slots(size_t extra_operand_slots, Args&&... args) { VERIFY(!is_current_block_terminated()); - size_t size_to_allocate = round_up_to_power_of_two(sizeof(OpType) + extra_register_slots * sizeof(Register), alignof(void*)); + size_t size_to_allocate = round_up_to_power_of_two(sizeof(OpType) + extra_operand_slots * sizeof(Operand), alignof(void*)); size_t slot_offset = m_current_basic_block->size(); grow(size_to_allocate); void* slot = m_current_basic_block->data() + slot_offset; @@ -92,30 +95,29 @@ public: op->set_source_record({ m_current_ast_node->start_offset(), m_current_ast_node->end_offset() }); } - struct ReferenceRegisters { - Register base; // [[Base]] - Optional referenced_name; // [[ReferencedName]] - Register this_value; // [[ThisValue]] + struct ReferenceOperands { + Optional base {}; // [[Base]] + Optional referenced_name {}; // [[ReferencedName]] as an operand + Optional referenced_identifier {}; // [[ReferencedName]] as an identifier + Optional referenced_private_identifier {}; // [[ReferencedName]] as a private identifier + Optional this_value {}; // [[ThisValue]] + Optional loaded_value {}; // Loaded value, if we've performed a load. }; - enum class CollectRegisters { - Yes, - No - }; - CodeGenerationErrorOr> emit_load_from_reference(JS::ASTNode const&, CollectRegisters); - CodeGenerationErrorOr> emit_store_to_reference(JS::ASTNode const&); - CodeGenerationErrorOr> emit_store_to_reference(ReferenceRegisters const&); + CodeGenerationErrorOr emit_load_from_reference(JS::ASTNode const&, Optional preferred_dst = {}); + CodeGenerationErrorOr emit_store_to_reference(JS::ASTNode const&, Operand value); + CodeGenerationErrorOr emit_store_to_reference(ReferenceOperands const&, Operand value); CodeGenerationErrorOr> emit_delete_reference(JS::ASTNode const&); - CodeGenerationErrorOr emit_super_reference(MemberExpression const&); + CodeGenerationErrorOr emit_super_reference(MemberExpression 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 emit_set_variable(JS::Identifier const& identifier, Operand value, 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 push_home_object(Operand); void pop_home_object(); - void emit_new_function(JS::FunctionExpression const&, Optional lhs_name); + void emit_new_function(Operand dst, JS::FunctionExpression const&, Optional lhs_name); - CodeGenerationErrorOr emit_named_evaluation_if_anonymous_function(Expression const&, Optional lhs_name); + CodeGenerationErrorOr> emit_named_evaluation_if_anonymous_function(Expression const&, Optional lhs_name, Optional preferred_dst = {}); void begin_continuable_scope(Label continue_target, Vector const& language_label_set); void end_continuable_scope(); @@ -231,21 +233,28 @@ public: m_boundaries.take_last(); } - void emit_get_by_id(IdentifierTableIndex); - void emit_get_by_id_with_this(IdentifierTableIndex, Register); + void emit_get_by_id(Operand dst, Operand base, IdentifierTableIndex); - void emit_iterator_value(); - void emit_iterator_complete(); + void emit_get_by_id_with_this(Operand dst, Operand base, IdentifierTableIndex, Operand this_value); + + void emit_iterator_value(Operand dst, Operand result); + void emit_iterator_complete(Operand dst, Operand result); [[nodiscard]] size_t next_global_variable_cache() { return m_next_global_variable_cache++; } [[nodiscard]] size_t next_environment_variable_cache() { return m_next_environment_variable_cache++; } [[nodiscard]] size_t next_property_lookup_cache() { return m_next_property_lookup_cache++; } - [[nodiscard]] Operand add_constant(Value value) + enum class DeduplicateConstant { + Yes, + No, + }; + [[nodiscard]] Operand add_constant(Value value, DeduplicateConstant deduplicate_constant = DeduplicateConstant::Yes) { - for (size_t i = 0; i < m_constants.size(); ++i) { - if (m_constants[i] == value) - return Operand(Operand::Type::Constant, i); + if (deduplicate_constant == DeduplicateConstant::Yes) { + for (size_t i = 0; i < m_constants.size(); ++i) { + if (m_constants[i] == value) + return Operand(Operand::Type::Constant, i); + } } m_constants.append(value); return Operand(Operand::Type::Constant, m_constants.size() - 1); @@ -288,7 +297,9 @@ private: Vector m_continuable_scopes; Vector m_breakable_scopes; Vector m_boundaries; - Vector m_home_objects; + Vector m_home_objects; + + HashTable m_initialized_locals; }; } diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h index 54d4758983..158f68c041 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.h +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h @@ -14,7 +14,7 @@ #define ENUMERATE_BYTECODE_OPS(O) \ O(Add) \ - O(Append) \ + O(ArrayAppend) \ O(AsyncIteratorClose) \ O(Await) \ O(BitwiseAnd) \ @@ -37,6 +37,8 @@ O(DeleteByValueWithThis) \ O(DeleteVariable) \ O(Div) \ + O(Dump) \ + O(End) \ O(EnterUnwindContext) \ O(EnterObjectEnvironment) \ O(Exp) \ @@ -55,7 +57,6 @@ O(GetPrivateById) \ O(GetVariable) \ O(GetGlobal) \ - O(GetLocal) \ O(GreaterThan) \ O(GreaterThanEquals) \ O(HasPrivateId) \ @@ -67,7 +68,7 @@ O(IteratorNext) \ O(IteratorToArray) \ O(Jump) \ - O(JumpConditional) \ + O(JumpIf) \ O(JumpNullish) \ O(JumpUndefined) \ O(LeaveLexicalEnvironment) \ @@ -75,11 +76,10 @@ O(LeftShift) \ O(LessThan) \ O(LessThanEquals) \ - O(Load) \ - O(LoadImmediate) \ O(LooselyEquals) \ O(LooselyInequals) \ O(Mod) \ + O(Mov) \ O(Mul) \ O(NewArray) \ O(NewBigInt) \ @@ -103,7 +103,6 @@ O(ScheduleJump) \ O(SetVariable) \ O(SetLocal) \ - O(Store) \ O(StrictlyEquals) \ O(StrictlyInequals) \ O(Sub) \ @@ -111,10 +110,10 @@ O(Throw) \ O(ThrowIfNotObject) \ O(ThrowIfNullish) \ + O(ThrowIfTDZ) \ O(ToNumeric) \ O(Typeof) \ O(TypeofVariable) \ - O(TypeofLocal) \ O(UnaryMinus) \ O(UnaryPlus) \ O(UnsignedRightShift) \ diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp index d33b2a6e30..50cb939283 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -39,6 +39,44 @@ namespace JS::Bytecode { bool g_dump_bytecode = false; +static ByteString format_operand(StringView name, Operand operand, Bytecode::Executable const& executable) +{ + StringBuilder builder; + builder.appendff("\033[32m{}\033[0m:", name); + switch (operand.type()) { + case Operand::Type::Register: + builder.appendff("\033[33mreg{}\033[0m", operand.index()); + break; + case Operand::Type::Local: + // FIXME: Show local name. + builder.appendff("\033[34mloc{}\033[0m", operand.index()); + break; + case Operand::Type::Constant: { + builder.append("\033[36m"sv); + auto value = executable.constants[operand.index()]; + if (value.is_empty()) + builder.append(""sv); + else if (value.is_boolean()) + builder.appendff("Bool({})", value.as_bool() ? "true"sv : "false"sv); + else if (value.is_int32()) + builder.appendff("Int32({})", value.as_i32()); + else if (value.is_double()) + builder.appendff("Double({})", value.as_double()); + else if (value.is_undefined()) + builder.append("Undefined"sv); + else if (value.is_null()) + builder.append("Null"sv); + else + builder.appendff("Value: {}", value); + builder.append("\033[0m"sv); + break; + } + default: + VERIFY_NOT_REACHED(); + } + return builder.to_byte_string(); +} + NonnullOwnPtr CallFrame::create(size_t register_count) { size_t allocation_size = sizeof(CallFrame) + sizeof(Value) * register_count; @@ -67,7 +105,7 @@ void Interpreter::visit_edges(Cell::Visitor& visitor) } } -Value Interpreter::get(Operand op) const +ALWAYS_INLINE Value Interpreter::get(Operand op) const { switch (op.type()) { case Operand::Type::Register: @@ -77,10 +115,10 @@ Value Interpreter::get(Operand op) const case Operand::Type::Constant: return current_executable().constants[op.index()]; } - VERIFY_NOT_REACHED(); + __builtin_unreachable(); } -void Interpreter::set(Operand op, Value value) +ALWAYS_INLINE void Interpreter::set(Operand op, Value value) { switch (op.type()) { case Operand::Type::Register: @@ -92,7 +130,7 @@ void Interpreter::set(Operand op, Value value) case Operand::Type::Constant: VERIFY_NOT_REACHED(); } - VERIFY_NOT_REACHED(); + __builtin_unreachable(); } // 16.1.6 ScriptEvaluation ( scriptRecord ), https://tc39.es/ecma262/#sec-runtime-semantics-scriptevaluation @@ -144,7 +182,7 @@ ThrowCompletionOr Interpreter::run(Script& script_record, JS::GCPtr Interpreter::run(SourceTextModule& module) return js_undefined(); } -void Interpreter::run_bytecode() +FLATTEN void Interpreter::run_bytecode() { auto* locals = vm().running_execution_context().locals.data(); - auto* registers = this->registers().data(); auto& accumulator = this->accumulator(); for (;;) { start: @@ -239,45 +276,32 @@ void Interpreter::run_bytecode() auto& instruction = *pc; switch (instruction.type()) { - case Instruction::Type::GetLocal: { - auto& local = locals[static_cast(instruction).index()]; - if (local.is_empty()) { - auto const& variable_name = vm().running_execution_context().function->local_variables_names()[static_cast(instruction).index()]; - result = vm().throw_completion(ErrorType::BindingNotInitialized, variable_name); - break; - } - accumulator = local; - break; - } case Instruction::Type::SetLocal: - locals[static_cast(instruction).index()] = accumulator; + locals[static_cast(instruction).index()] = get(static_cast(instruction).src()); break; - case Instruction::Type::Load: - accumulator = registers[static_cast(instruction).src().index()]; - break; - case Instruction::Type::Store: - registers[static_cast(instruction).dst().index()] = accumulator; - break; - case Instruction::Type::LoadImmediate: - accumulator = static_cast(instruction).value(); + case Instruction::Type::Mov: + set(static_cast(instruction).dst(), get(static_cast(instruction).src())); break; + case Instruction::Type::End: + accumulator = get(static_cast(instruction).value()); + return; case Instruction::Type::Jump: m_current_block = &static_cast(instruction).true_target()->block(); goto start; - case Instruction::Type::JumpConditional: - if (accumulator.to_boolean()) - m_current_block = &static_cast(instruction).true_target()->block(); + case Instruction::Type::JumpIf: + if (get(static_cast(instruction).condition()).to_boolean()) + m_current_block = &static_cast(instruction).true_target()->block(); else - m_current_block = &static_cast(instruction).false_target()->block(); + m_current_block = &static_cast(instruction).false_target()->block(); goto start; case Instruction::Type::JumpNullish: - if (accumulator.is_nullish()) + if (get(static_cast(instruction).condition()).is_nullish()) m_current_block = &static_cast(instruction).true_target()->block(); else m_current_block = &static_cast(instruction).false_target()->block(); goto start; case Instruction::Type::JumpUndefined: - if (accumulator.is_undefined()) + if (get(static_cast(instruction).condition()).is_undefined()) m_current_block = &static_cast(instruction).true_target()->block(); else m_current_block = &static_cast(instruction).false_target()->block(); @@ -458,9 +482,9 @@ void Interpreter::leave_unwind_context() unwind_contexts().take_last(); } -void Interpreter::catch_exception() +void Interpreter::catch_exception(Operand dst) { - accumulator() = reg(Register::exception()); + set(dst, reg(Register::exception())); reg(Register::exception()) = {}; auto& context = unwind_contexts().last(); VERIFY(!context.handler_called); @@ -476,9 +500,9 @@ void Interpreter::enter_object_environment(Object& object) vm().running_execution_context().lexical_environment = new_object_environment(object, true, old_environment); } -ThrowCompletionOr> compile(VM& vm, ASTNode const& node, FunctionKind kind, DeprecatedFlyString const& name) +ThrowCompletionOr> compile(VM& vm, ASTNode const& node, ReadonlySpan parameters, FunctionKind kind, DeprecatedFlyString const& name) { - auto executable_result = Bytecode::Generator::generate(vm, node, kind); + auto executable_result = Bytecode::Generator::generate(vm, node, parameters, kind); if (executable_result.is_error()) return vm.throw_completion(ErrorType::NotImplemented, TRY_OR_THROW_OOM(vm, executable_result.error().to_string())); @@ -533,19 +557,32 @@ ByteString Instruction::to_byte_string(Bytecode::Executable const& executable) c namespace JS::Bytecode::Op { -ThrowCompletionOr Load::execute_impl(Bytecode::Interpreter&) const +static void dump_object(Object& o, HashTable& seen, int indent = 0) { - // Handled in the interpreter loop. - __builtin_unreachable(); + if (seen.contains(&o)) + return; + seen.set(&o); + for (auto& it : o.shape().property_table()) { + auto value = o.get_direct(it.value.offset); + dbgln("{} {} -> {}", String::repeated(' ', indent).release_value(), it.key.to_display_string(), value); + if (value.is_object()) { + dump_object(value.as_object(), seen, indent + 2); + } + } } -ThrowCompletionOr LoadImmediate::execute_impl(Bytecode::Interpreter&) const +ThrowCompletionOr Dump::execute_impl(Bytecode::Interpreter& interpreter) const { - // Handled in the interpreter loop. - __builtin_unreachable(); + auto value = interpreter.get(m_value); + dbgln("(DUMP) {}: {}", m_text, value); + if (value.is_object()) { + HashTable seen; + dump_object(value.as_object(), seen); + } + return {}; } -ThrowCompletionOr Store::execute_impl(Bytecode::Interpreter&) const +ThrowCompletionOr End::execute_impl(Bytecode::Interpreter&) const { // Handled in the interpreter loop. __builtin_unreachable(); @@ -575,14 +612,17 @@ static ThrowCompletionOr strict_equals(VM&, Value src1, Value src2) ThrowCompletionOr OpTitleCase::execute_impl(Bytecode::Interpreter& interpreter) const \ { \ auto& vm = interpreter.vm(); \ - auto lhs = interpreter.reg(m_lhs_reg); \ - auto rhs = interpreter.accumulator(); \ - interpreter.accumulator() = TRY(op_snake_case(vm, lhs, rhs)); \ + auto lhs = interpreter.get(m_lhs); \ + auto rhs = interpreter.get(m_rhs); \ + interpreter.set(m_dst, TRY(op_snake_case(vm, lhs, rhs))); \ return {}; \ } \ - ByteString OpTitleCase::to_byte_string_impl(Bytecode::Executable const&) const \ + ByteString OpTitleCase::to_byte_string_impl(Bytecode::Executable const& executable) const \ { \ - return ByteString::formatted(#OpTitleCase " {}", m_lhs_reg); \ + return ByteString::formatted(#OpTitleCase " {}, {}, {}", \ + format_operand("dst"sv, m_dst, executable), \ + format_operand("lhs"sv, m_lhs, executable), \ + format_operand("rhs"sv, m_rhs, executable)); \ } JS_ENUMERATE_COMMON_BINARY_OPS(JS_DEFINE_COMMON_BINARY_OP) @@ -601,12 +641,14 @@ static ThrowCompletionOr typeof_(VM& vm, Value value) ThrowCompletionOr OpTitleCase::execute_impl(Bytecode::Interpreter& interpreter) const \ { \ auto& vm = interpreter.vm(); \ - interpreter.accumulator() = TRY(op_snake_case(vm, interpreter.accumulator())); \ + interpreter.set(dst(), TRY(op_snake_case(vm, interpreter.get(src())))); \ return {}; \ } \ - ByteString OpTitleCase::to_byte_string_impl(Bytecode::Executable const&) const \ + ByteString OpTitleCase::to_byte_string_impl(Bytecode::Executable const& executable) const \ { \ - return #OpTitleCase; \ + return ByteString::formatted(#OpTitleCase " {}, {}", \ + format_operand("dst"sv, dst(), executable), \ + format_operand("src"sv, src(), executable)); \ } JS_ENUMERATE_COMMON_UNARY_OPS(JS_DEFINE_COMMON_UNARY_OP) @@ -614,7 +656,7 @@ JS_ENUMERATE_COMMON_UNARY_OPS(JS_DEFINE_COMMON_UNARY_OP) ThrowCompletionOr NewBigInt::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); - interpreter.accumulator() = BigInt::create(vm, m_bigint); + interpreter.set(dst(), BigInt::create(vm, m_bigint)); return {}; } @@ -625,7 +667,7 @@ ThrowCompletionOr NewArray::execute_impl(Bytecode::Interpreter& interprete auto& value = interpreter.reg(Register(m_elements[0].index() + i)); array->indexed_properties().put(i, value, default_attributes); } - interpreter.accumulator() = array; + interpreter.set(dst(), array); return {}; } @@ -634,33 +676,33 @@ ThrowCompletionOr NewPrimitiveArray::execute_impl(Bytecode::Interpreter& i auto array = MUST(Array::create(interpreter.realm(), 0)); for (size_t i = 0; i < m_values.size(); i++) array->indexed_properties().put(i, m_values[i], default_attributes); - interpreter.accumulator() = array; + interpreter.set(dst(), array); return {}; } -ThrowCompletionOr Append::execute_impl(Bytecode::Interpreter& interpreter) const +ThrowCompletionOr ArrayAppend::execute_impl(Bytecode::Interpreter& interpreter) const { - return append(interpreter.vm(), interpreter.reg(m_lhs), interpreter.accumulator(), m_is_spread); + return append(interpreter.vm(), interpreter.get(dst()), interpreter.get(src()), m_is_spread); } ThrowCompletionOr ImportCall::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); - auto specifier = interpreter.reg(m_specifier); - auto options_value = interpreter.reg(m_options); - interpreter.accumulator() = TRY(perform_import_call(vm, specifier, options_value)); + auto specifier = interpreter.get(m_specifier); + auto options_value = interpreter.get(m_options); + interpreter.set(dst(), TRY(perform_import_call(vm, specifier, options_value))); return {}; } ThrowCompletionOr IteratorToArray::execute_impl(Bytecode::Interpreter& interpreter) const { - interpreter.accumulator() = TRY(iterator_to_array(interpreter.vm(), interpreter.accumulator())); + interpreter.set(dst(), TRY(iterator_to_array(interpreter.vm(), interpreter.get(iterator())))); return {}; } ThrowCompletionOr NewString::execute_impl(Bytecode::Interpreter& interpreter) const { - interpreter.accumulator() = PrimitiveString::create(interpreter.vm(), interpreter.current_executable().get_string(m_string)); + interpreter.set(dst(), PrimitiveString::create(interpreter.vm(), interpreter.current_executable().get_string(m_string))); return {}; } @@ -668,32 +710,34 @@ ThrowCompletionOr NewObject::execute_impl(Bytecode::Interpreter& interpret { auto& vm = interpreter.vm(); auto& realm = *vm.current_realm(); - - interpreter.accumulator() = Object::create(realm, realm.intrinsics().object_prototype()); + interpreter.set(dst(), Object::create(realm, realm.intrinsics().object_prototype())); return {}; } ThrowCompletionOr NewRegExp::execute_impl(Bytecode::Interpreter& interpreter) const { - interpreter.accumulator() = new_regexp( - interpreter.vm(), - interpreter.current_executable().regex_table->get(m_regex_index), - interpreter.current_executable().get_string(m_source_index), - interpreter.current_executable().get_string(m_flags_index)); + interpreter.set(dst(), + new_regexp( + interpreter.vm(), + interpreter.current_executable().regex_table->get(m_regex_index), + interpreter.current_executable().get_string(m_source_index), + interpreter.current_executable().get_string(m_flags_index))); return {}; } -#define JS_DEFINE_NEW_BUILTIN_ERROR_OP(ErrorName) \ - ThrowCompletionOr New##ErrorName::execute_impl(Bytecode::Interpreter& interpreter) const \ - { \ - auto& vm = interpreter.vm(); \ - auto& realm = *vm.current_realm(); \ - interpreter.accumulator() = ErrorName::create(realm, interpreter.current_executable().get_string(m_error_string)); \ - return {}; \ - } \ - ByteString New##ErrorName::to_byte_string_impl(Bytecode::Executable const& executable) const \ - { \ - return ByteString::formatted("New" #ErrorName " {} (\"{}\")", m_error_string, executable.string_table->get(m_error_string)); \ +#define JS_DEFINE_NEW_BUILTIN_ERROR_OP(ErrorName) \ + ThrowCompletionOr New##ErrorName::execute_impl(Bytecode::Interpreter& interpreter) const \ + { \ + auto& vm = interpreter.vm(); \ + auto& realm = *vm.current_realm(); \ + interpreter.set(dst(), ErrorName::create(realm, interpreter.current_executable().get_string(m_error_string))); \ + return {}; \ + } \ + ByteString New##ErrorName::to_byte_string_impl(Bytecode::Executable const& executable) const \ + { \ + return ByteString::formatted("New" #ErrorName " {}, {}", \ + format_operand("dst"sv, m_dst, executable), \ + executable.string_table->get(m_error_string)); \ } JS_ENUMERATE_NEW_BUILTIN_ERROR_OPS(JS_DEFINE_NEW_BUILTIN_ERROR_OP) @@ -703,35 +747,32 @@ ThrowCompletionOr CopyObjectExcludingProperties::execute_impl(Bytecode::In auto& vm = interpreter.vm(); auto& realm = *vm.current_realm(); - auto from_object = interpreter.reg(m_from_object); + auto from_object = interpreter.get(m_from_object); auto to_object = Object::create(realm, realm.intrinsics().object_prototype()); HashTable excluded_names; for (size_t i = 0; i < m_excluded_names_count; ++i) { - excluded_names.set(TRY(interpreter.reg(m_excluded_names[i]).to_property_key(vm))); + excluded_names.set(TRY(interpreter.get(m_excluded_names[i]).to_property_key(vm))); } TRY(to_object->copy_data_properties(vm, from_object, excluded_names)); - interpreter.accumulator() = to_object; + interpreter.set(dst(), to_object); return {}; } ThrowCompletionOr ConcatString::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); - auto string = TRY(interpreter.accumulator().to_primitive_string(vm)); - interpreter.reg(m_lhs) = PrimitiveString::create(vm, interpreter.reg(m_lhs).as_string(), string); + auto string = TRY(interpreter.get(src()).to_primitive_string(vm)); + interpreter.set(dst(), PrimitiveString::create(vm, interpreter.get(dst()).as_string(), string)); return {}; } ThrowCompletionOr GetVariable::execute_impl(Bytecode::Interpreter& interpreter) const { - interpreter.accumulator() = TRY(get_variable( - interpreter, - interpreter.current_executable().get_identifier(m_identifier), - interpreter.current_executable().environment_variable_caches[m_cache_index])); + interpreter.set(dst(), TRY(get_variable(interpreter, interpreter.current_executable().get_identifier(m_identifier), interpreter.current_executable().environment_variable_caches[m_cache_index]))); return {}; } @@ -741,32 +782,23 @@ ThrowCompletionOr GetCalleeAndThisFromEnvironment::execute_impl(Bytecode:: interpreter, interpreter.current_executable().get_identifier(m_identifier), interpreter.current_executable().environment_variable_caches[m_cache_index])); - interpreter.reg(m_callee_reg) = callee_and_this.callee; - interpreter.reg(m_this_reg) = callee_and_this.this_value; + interpreter.set(m_callee, callee_and_this.callee); + interpreter.set(m_this_value, callee_and_this.this_value); return {}; } ThrowCompletionOr GetGlobal::execute_impl(Bytecode::Interpreter& interpreter) const { - interpreter.accumulator() = TRY(get_global( - interpreter, - interpreter.current_executable().get_identifier(m_identifier), - interpreter.current_executable().global_variable_caches[m_cache_index])); + interpreter.set(dst(), TRY(get_global(interpreter, interpreter.current_executable().get_identifier(m_identifier), interpreter.current_executable().global_variable_caches[m_cache_index]))); return {}; } -ThrowCompletionOr GetLocal::execute_impl(Bytecode::Interpreter&) const -{ - // Handled in the interpreter loop. - __builtin_unreachable(); -} - ThrowCompletionOr DeleteVariable::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); auto const& string = interpreter.current_executable().get_identifier(m_identifier); auto reference = TRY(vm.resolve_binding(string)); - interpreter.accumulator() = Value(TRY(reference.delete_(vm))); + interpreter.set(dst(), Value(TRY(reference.delete_(vm)))); return {}; } @@ -783,14 +815,14 @@ ThrowCompletionOr CreateLexicalEnvironment::execute_impl(Bytecode::Interpr ThrowCompletionOr EnterObjectEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const { - auto object = TRY(interpreter.accumulator().to_object(interpreter.vm())); + auto object = TRY(interpreter.get(m_object).to_object(interpreter.vm())); interpreter.enter_object_environment(*object); return {}; } ThrowCompletionOr Catch::execute_impl(Bytecode::Interpreter& interpreter) const { - interpreter.catch_exception(); + interpreter.catch_exception(dst()); return {}; } @@ -806,7 +838,7 @@ ThrowCompletionOr SetVariable::execute_impl(Bytecode::Interpreter& interpr auto const& name = interpreter.current_executable().get_identifier(m_identifier); TRY(set_variable(vm, name, - interpreter.accumulator(), + interpreter.get(src()), m_mode, m_initialization_mode, interpreter.current_executable().environment_variable_caches[m_cache_index])); @@ -821,18 +853,18 @@ ThrowCompletionOr SetLocal::execute_impl(Bytecode::Interpreter&) const ThrowCompletionOr GetById::execute_impl(Bytecode::Interpreter& interpreter) const { - auto base_value = interpreter.accumulator(); + auto base_value = interpreter.get(base()); auto& cache = interpreter.current_executable().property_lookup_caches[m_cache_index]; - interpreter.accumulator() = TRY(get_by_id(interpreter.vm(), interpreter.current_executable().get_identifier(m_property), base_value, base_value, cache)); + interpreter.set(dst(), TRY(get_by_id(interpreter.vm(), interpreter.current_executable().get_identifier(m_property), base_value, base_value, cache))); return {}; } ThrowCompletionOr GetByIdWithThis::execute_impl(Bytecode::Interpreter& interpreter) const { - auto base_value = interpreter.accumulator(); - auto this_value = interpreter.reg(m_this_value); + auto base_value = interpreter.get(m_base); + auto this_value = interpreter.get(m_this_value); auto& cache = interpreter.current_executable().property_lookup_caches[m_cache_index]; - interpreter.accumulator() = TRY(get_by_id(interpreter.vm(), interpreter.current_executable().get_identifier(m_property), base_value, this_value, cache)); + interpreter.set(dst(), TRY(get_by_id(interpreter.vm(), interpreter.current_executable().get_identifier(m_property), base_value, this_value, cache))); return {}; } @@ -840,9 +872,9 @@ ThrowCompletionOr GetPrivateById::execute_impl(Bytecode::Interpreter& inte { auto& vm = interpreter.vm(); auto const& name = interpreter.current_executable().get_identifier(m_property); - auto base_value = interpreter.accumulator(); + auto base_value = interpreter.get(m_base); auto private_reference = make_private_reference(vm, base_value, name); - interpreter.accumulator() = TRY(private_reference.get_value(vm)); + interpreter.set(dst(), TRY(private_reference.get_value(vm))); return {}; } @@ -850,70 +882,65 @@ ThrowCompletionOr HasPrivateId::execute_impl(Bytecode::Interpreter& interp { auto& vm = interpreter.vm(); - if (!interpreter.accumulator().is_object()) + auto base = interpreter.get(m_base); + if (!base.is_object()) return vm.throw_completion(ErrorType::InOperatorWithObject); auto private_environment = vm.running_execution_context().private_environment; VERIFY(private_environment); auto private_name = private_environment->resolve_private_identifier(interpreter.current_executable().get_identifier(m_property)); - interpreter.accumulator() = Value(interpreter.accumulator().as_object().private_element_find(private_name) != nullptr); + interpreter.set(dst(), Value(base.as_object().private_element_find(private_name) != nullptr)); return {}; } ThrowCompletionOr PutById::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 value = interpreter.get(m_src); + auto base = interpreter.get(m_base); PropertyKey name = interpreter.current_executable().get_identifier(m_property); auto& cache = interpreter.current_executable().property_lookup_caches[m_cache_index]; TRY(put_by_property_key(vm, base, base, value, name, m_kind, &cache)); - 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); + auto value = interpreter.get(m_src); + auto base = interpreter.get(m_base); PropertyKey name = interpreter.current_executable().get_identifier(m_property); auto& cache = interpreter.current_executable().property_lookup_caches[m_cache_index]; - TRY(put_by_property_key(vm, base, interpreter.reg(m_this_value), value, name, m_kind, &cache)); - interpreter.accumulator() = value; + TRY(put_by_property_key(vm, base, interpreter.get(m_this_value), value, name, m_kind, &cache)); return {}; } ThrowCompletionOr PutPrivateById::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 object = TRY(interpreter.reg(m_base).to_object(vm)); + auto value = interpreter.get(m_src); + auto object = TRY(interpreter.get(m_base).to_object(vm)); auto name = interpreter.current_executable().get_identifier(m_property); auto private_reference = make_private_reference(vm, object, name); TRY(private_reference.put_value(vm, value)); - interpreter.accumulator() = value; return {}; } ThrowCompletionOr DeleteById::execute_impl(Bytecode::Interpreter& interpreter) const { - auto base_value = interpreter.accumulator(); - interpreter.accumulator() = TRY(Bytecode::delete_by_id(interpreter, base_value, m_property)); + auto base_value = interpreter.get(m_base); + interpreter.set(dst(), TRY(Bytecode::delete_by_id(interpreter, base_value, m_property))); return {}; } ThrowCompletionOr DeleteByIdWithThis::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); - auto base_value = interpreter.accumulator(); + auto base_value = interpreter.get(m_base); auto const& identifier = interpreter.current_executable().get_identifier(m_property); bool strict = vm.in_strict_mode(); - auto reference = Reference { base_value, identifier, interpreter.reg(m_this_value), strict }; - interpreter.accumulator() = Value(TRY(reference.delete_(vm))); + auto reference = Reference { base_value, identifier, interpreter.get(m_this_value), strict }; + interpreter.set(dst(), Value(TRY(reference.delete_(vm)))); return {}; } @@ -932,7 +959,7 @@ ThrowCompletionOr ResolveThisBinding::execute_impl(Bytecode::Interpreter& auto& vm = interpreter.vm(); cached_this_value = TRY(vm.resolve_this_binding()); } - interpreter.accumulator() = cached_this_value; + interpreter.set(dst(), cached_this_value); return {}; } @@ -948,24 +975,30 @@ ThrowCompletionOr ResolveSuperBase::execute_impl(Bytecode::Interpreter& in VERIFY(env.has_super_binding()); // 3. Let baseValue be ? env.GetSuperBase(). - interpreter.accumulator() = TRY(env.get_super_base()); + interpreter.set(dst(), TRY(env.get_super_base())); return {}; } ThrowCompletionOr GetNewTarget::execute_impl(Bytecode::Interpreter& interpreter) const { - interpreter.accumulator() = interpreter.vm().get_new_target(); + interpreter.set(dst(), interpreter.vm().get_new_target()); return {}; } ThrowCompletionOr GetImportMeta::execute_impl(Bytecode::Interpreter& interpreter) const { - interpreter.accumulator() = interpreter.vm().get_import_meta(); + interpreter.set(dst(), interpreter.vm().get_import_meta()); return {}; } -ThrowCompletionOr JumpConditional::execute_impl(Bytecode::Interpreter&) const +ThrowCompletionOr JumpIf::execute_impl(Bytecode::Interpreter&) const +{ + // Handled in the interpreter loop. + __builtin_unreachable(); +} + +ThrowCompletionOr JumpUndefined::execute_impl(Bytecode::Interpreter&) const { // Handled in the interpreter loop. __builtin_unreachable(); @@ -977,7 +1010,7 @@ ThrowCompletionOr JumpNullish::execute_impl(Bytecode::Interpreter&) const __builtin_unreachable(); } -ThrowCompletionOr JumpUndefined::execute_impl(Bytecode::Interpreter&) const +ThrowCompletionOr Mov::execute_impl(Bytecode::Interpreter&) const { // Handled in the interpreter loop. __builtin_unreachable(); @@ -1012,58 +1045,61 @@ static ThrowCompletionOr dispatch_builtin_call(Bytecode::Interpreter& int ThrowCompletionOr Call::execute_impl(Bytecode::Interpreter& interpreter) const { - auto callee = interpreter.reg(m_callee); + auto callee = interpreter.get(m_callee); TRY(throw_if_needed_for_call(interpreter, callee, call_type(), expression_string())); if (m_builtin.has_value() && m_argument_count == Bytecode::builtin_argument_count(m_builtin.value()) && interpreter.realm().get_builtin_value(m_builtin.value()) == callee) { - interpreter.accumulator() = TRY(dispatch_builtin_call(interpreter, m_builtin.value(), m_first_argument)); + interpreter.set(dst(), TRY(dispatch_builtin_call(interpreter, m_builtin.value(), m_first_argument))); return {}; } - interpreter.accumulator() = TRY(perform_call(interpreter, interpreter.reg(m_this_value), call_type(), callee, interpreter.registers().slice(m_first_argument.index(), m_argument_count))); + interpreter.set(dst(), TRY(perform_call(interpreter, interpreter.get(m_this_value), call_type(), callee, interpreter.registers().slice(m_first_argument.index(), m_argument_count)))); return {}; } ThrowCompletionOr CallWithArgumentArray::execute_impl(Bytecode::Interpreter& interpreter) const { - auto callee = interpreter.reg(m_callee); + auto callee = interpreter.get(m_callee); TRY(throw_if_needed_for_call(interpreter, callee, call_type(), expression_string())); - auto argument_values = argument_list_evaluation(interpreter.vm(), interpreter.accumulator()); - interpreter.accumulator() = TRY(perform_call(interpreter, interpreter.reg(m_this_value), call_type(), callee, move(argument_values))); + auto argument_values = argument_list_evaluation(interpreter.vm(), interpreter.get(arguments())); + interpreter.set(dst(), TRY(perform_call(interpreter, interpreter.get(m_this_value), call_type(), callee, move(argument_values)))); return {}; } // 13.3.7.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation ThrowCompletionOr SuperCallWithArgumentArray::execute_impl(Bytecode::Interpreter& interpreter) const { - interpreter.accumulator() = TRY(super_call_with_argument_array(interpreter.vm(), interpreter.accumulator(), m_is_synthetic)); + interpreter.set(dst(), TRY(super_call_with_argument_array(interpreter.vm(), interpreter.get(arguments()), m_is_synthetic))); return {}; } ThrowCompletionOr NewFunction::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); - interpreter.accumulator() = new_function(vm, m_function_node, m_lhs_name, m_home_object); + interpreter.set(dst(), new_function(vm, m_function_node, m_lhs_name, m_home_object)); return {}; } ThrowCompletionOr Return::execute_impl(Bytecode::Interpreter& interpreter) const { - interpreter.do_return(interpreter.accumulator().value_or(js_undefined())); + if (m_value.has_value()) + interpreter.do_return(interpreter.get(*m_value)); + else + interpreter.do_return(js_undefined()); return {}; } ThrowCompletionOr Increment::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); - auto old_value = interpreter.accumulator(); + auto old_value = interpreter.get(dst()); // OPTIMIZATION: Fast path for Int32 values. if (old_value.is_int32()) { auto integer_value = old_value.as_i32(); if (integer_value != NumericLimits::max()) [[likely]] { - interpreter.accumulator() = Value { integer_value + 1 }; + interpreter.set(dst(), Value { integer_value + 1 }); return {}; } } @@ -1071,46 +1107,58 @@ ThrowCompletionOr Increment::execute_impl(Bytecode::Interpreter& interpret old_value = TRY(old_value.to_numeric(vm)); if (old_value.is_number()) - interpreter.accumulator() = Value(old_value.as_double() + 1); + interpreter.set(dst(), Value(old_value.as_double() + 1)); else - interpreter.accumulator() = BigInt::create(vm, old_value.as_bigint().big_integer().plus(Crypto::SignedBigInteger { 1 })); + interpreter.set(dst(), BigInt::create(vm, old_value.as_bigint().big_integer().plus(Crypto::SignedBigInteger { 1 }))); return {}; } ThrowCompletionOr Decrement::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); - auto old_value = TRY(interpreter.accumulator().to_numeric(vm)); + auto old_value = interpreter.get(dst()); + + old_value = TRY(old_value.to_numeric(vm)); if (old_value.is_number()) - interpreter.accumulator() = Value(old_value.as_double() - 1); + interpreter.set(dst(), Value(old_value.as_double() - 1)); else - interpreter.accumulator() = BigInt::create(vm, old_value.as_bigint().big_integer().minus(Crypto::SignedBigInteger { 1 })); + interpreter.set(dst(), BigInt::create(vm, old_value.as_bigint().big_integer().minus(Crypto::SignedBigInteger { 1 }))); return {}; } ThrowCompletionOr Throw::execute_impl(Bytecode::Interpreter& interpreter) const { - return throw_completion(interpreter.accumulator()); + return throw_completion(interpreter.get(src())); } ThrowCompletionOr ThrowIfNotObject::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); - if (!interpreter.accumulator().is_object()) - return vm.throw_completion(ErrorType::NotAnObject, interpreter.accumulator().to_string_without_side_effects()); + auto src = interpreter.get(m_src); + if (!src.is_object()) + return vm.throw_completion(ErrorType::NotAnObject, src.to_string_without_side_effects()); return {}; } ThrowCompletionOr ThrowIfNullish::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); - auto value = interpreter.accumulator(); + auto value = interpreter.get(m_src); if (value.is_nullish()) return vm.throw_completion(ErrorType::NotObjectCoercible, value.to_string_without_side_effects()); return {}; } +ThrowCompletionOr ThrowIfTDZ::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto value = interpreter.get(m_src); + if (value.is_empty()) + return vm.throw_completion(ErrorType::BindingNotInitialized, value.to_string_without_side_effects()); + return {}; +} + ThrowCompletionOr EnterUnwindContext::execute_impl(Bytecode::Interpreter&) const { // Handled in the interpreter loop. @@ -1143,7 +1191,8 @@ ThrowCompletionOr ContinuePendingUnwind::execute_impl(Bytecode::Interprete ThrowCompletionOr Yield::execute_impl(Bytecode::Interpreter& interpreter) const { - auto yielded_value = interpreter.accumulator().value_or(js_undefined()); + auto yielded_value = interpreter.get(m_value).value_or(js_undefined()); + auto object = Object::create(interpreter.realm(), nullptr); object->define_direct_property("result", yielded_value, JS::default_attributes); @@ -1156,12 +1205,13 @@ ThrowCompletionOr Yield::execute_impl(Bytecode::Interpreter& interpreter) object->define_direct_property("isAwait", Value(false), JS::default_attributes); interpreter.do_return(object); + return {}; } ThrowCompletionOr Await::execute_impl(Bytecode::Interpreter& interpreter) const { - auto yielded_value = interpreter.accumulator().value_or(js_undefined()); + auto yielded_value = interpreter.get(m_argument).value_or(js_undefined()); auto object = Object::create(interpreter.realm(), nullptr); object->define_direct_property("result", yielded_value, JS::default_attributes); // FIXME: If we get a pointer, which is not accurately representable as a double @@ -1174,67 +1224,53 @@ ThrowCompletionOr Await::execute_impl(Bytecode::Interpreter& interpreter) ThrowCompletionOr GetByValue::execute_impl(Bytecode::Interpreter& interpreter) const { - interpreter.accumulator() = TRY(get_by_value(interpreter.vm(), interpreter.reg(m_base), interpreter.accumulator())); + interpreter.set(dst(), TRY(get_by_value(interpreter.vm(), interpreter.get(m_base), interpreter.get(m_property)))); 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_value = interpreter.get(m_property); + auto object = TRY(interpreter.get(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))); + interpreter.set(dst(), TRY(object->internal_get(property_key, interpreter.get(m_this_value)))); return {}; } ThrowCompletionOr PutByValue::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); - - auto value = interpreter.accumulator(); - TRY(put_by_value(vm, interpreter.reg(m_base), interpreter.reg(m_property), interpreter.accumulator(), m_kind)); - interpreter.accumulator() = value; - + auto value = interpreter.get(m_src); + TRY(put_by_value(vm, interpreter.get(m_base), interpreter.get(m_property), value, m_kind)); 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 = m_kind != PropertyKind::Spread ? TRY(interpreter.reg(m_property).to_property_key(vm)) : PropertyKey {}; - TRY(put_by_property_key(vm, base, interpreter.reg(m_this_value), value, property_key, m_kind)); - interpreter.accumulator() = value; + auto value = interpreter.get(m_src); + auto base = interpreter.get(m_base); + auto property_key = m_kind != PropertyKind::Spread ? TRY(interpreter.get(m_property).to_property_key(vm)) : PropertyKey {}; + TRY(put_by_property_key(vm, base, interpreter.get(m_this_value), value, property_key, m_kind)); return {}; } ThrowCompletionOr DeleteByValue::execute_impl(Bytecode::Interpreter& interpreter) const { - auto base_value = interpreter.reg(m_base); - auto property_key_value = interpreter.accumulator(); - interpreter.accumulator() = TRY(delete_by_value(interpreter, base_value, property_key_value)); + auto base_value = interpreter.get(m_base); + auto property_key_value = interpreter.get(m_property); + interpreter.set(dst(), TRY(delete_by_value(interpreter, base_value, property_key_value))); return {}; } ThrowCompletionOr DeleteByValueWithThis::execute_impl(Bytecode::Interpreter& interpreter) const { - // NOTE: Get the property key from the accumulator before side effects have a chance to overwrite it. - auto property_key_value = interpreter.accumulator(); - auto base_value = interpreter.reg(m_base); - auto this_value = interpreter.reg(m_this_value); - interpreter.accumulator() = TRY(delete_by_value_with_this(interpreter, base_value, property_key_value, this_value)); + auto property_key_value = interpreter.get(m_property); + auto base_value = interpreter.get(m_base); + auto this_value = interpreter.get(m_this_value); + interpreter.set(dst(), TRY(delete_by_value_with_this(interpreter, base_value, property_key_value, this_value))); return {}; } @@ -1242,21 +1278,21 @@ ThrowCompletionOr DeleteByValueWithThis::execute_impl(Bytecode::Interprete ThrowCompletionOr GetIterator::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); - interpreter.accumulator() = TRY(get_iterator(vm, interpreter.accumulator(), m_hint)); + interpreter.set(dst(), TRY(get_iterator(vm, interpreter.get(iterable()), m_hint))); return {}; } ThrowCompletionOr GetObjectFromIteratorRecord::execute_impl(Bytecode::Interpreter& interpreter) const { - auto& iterator_record = verify_cast(interpreter.reg(m_iterator_record).as_object()); - interpreter.reg(m_object) = iterator_record.iterator; + auto& iterator_record = verify_cast(interpreter.get(m_iterator_record).as_object()); + interpreter.set(m_object, iterator_record.iterator); return {}; } ThrowCompletionOr GetNextMethodFromIteratorRecord::execute_impl(Bytecode::Interpreter& interpreter) const { - auto& iterator_record = verify_cast(interpreter.reg(m_iterator_record).as_object()); - interpreter.reg(m_next_method) = iterator_record.next_method; + auto& iterator_record = verify_cast(interpreter.get(m_iterator_record).as_object()); + interpreter.set(m_next_method, iterator_record.next_method); return {}; } @@ -1264,21 +1300,21 @@ ThrowCompletionOr GetMethod::execute_impl(Bytecode::Interpreter& interpret { auto& vm = interpreter.vm(); auto identifier = interpreter.current_executable().get_identifier(m_property); - auto method = TRY(interpreter.accumulator().get_method(vm, identifier)); - interpreter.accumulator() = method ?: js_undefined(); + auto method = TRY(interpreter.get(m_object).get_method(vm, identifier)); + interpreter.set(dst(), method ?: js_undefined()); return {}; } ThrowCompletionOr GetObjectPropertyIterator::execute_impl(Bytecode::Interpreter& interpreter) const { - interpreter.accumulator() = TRY(get_object_property_iterator(interpreter.vm(), interpreter.accumulator())); + interpreter.set(dst(), TRY(get_object_property_iterator(interpreter.vm(), interpreter.get(object())))); return {}; } ThrowCompletionOr IteratorClose::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); - auto& iterator = verify_cast(interpreter.accumulator().as_object()); + auto& iterator = verify_cast(interpreter.get(m_iterator_record).as_object()); // FIXME: Return the value of the resulting completion. (Note that m_completion_value can be empty!) TRY(iterator_close(vm, iterator, Completion { m_completion_type, m_completion_value, {} })); @@ -1288,7 +1324,7 @@ ThrowCompletionOr IteratorClose::execute_impl(Bytecode::Interpreter& inter ThrowCompletionOr AsyncIteratorClose::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); - auto& iterator = verify_cast(interpreter.accumulator().as_object()); + auto& iterator = verify_cast(interpreter.get(m_iterator_record).as_object()); // FIXME: Return the value of the resulting completion. (Note that m_completion_value can be empty!) TRY(async_iterator_close(vm, iterator, Completion { m_completion_type, m_completion_value, {} })); @@ -1298,15 +1334,17 @@ ThrowCompletionOr AsyncIteratorClose::execute_impl(Bytecode::Interpreter& ThrowCompletionOr IteratorNext::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); - auto& iterator = verify_cast(interpreter.accumulator().as_object()); - - interpreter.accumulator() = TRY(iterator_next(vm, iterator)); + auto& iterator_record = verify_cast(interpreter.get(m_iterator_record).as_object()); + interpreter.set(dst(), TRY(iterator_next(vm, iterator_record))); return {}; } ThrowCompletionOr NewClass::execute_impl(Bytecode::Interpreter& interpreter) const { - interpreter.accumulator() = TRY(new_class(interpreter.vm(), interpreter.accumulator(), m_class_expression, m_lhs_name)); + Value super_class; + if (m_super_class.has_value()) + super_class = interpreter.get(m_super_class.value()); + interpreter.set(dst(), TRY(new_class(interpreter.vm(), super_class, m_class_expression, m_lhs_name))); return {}; } @@ -1314,21 +1352,13 @@ ThrowCompletionOr NewClass::execute_impl(Bytecode::Interpreter& interprete ThrowCompletionOr TypeofVariable::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); - interpreter.accumulator() = TRY(typeof_variable(vm, interpreter.current_executable().get_identifier(m_identifier))); - return {}; -} - -ThrowCompletionOr TypeofLocal::execute_impl(Bytecode::Interpreter& interpreter) const -{ - auto& vm = interpreter.vm(); - auto const& value = vm.running_execution_context().local(m_index); - interpreter.accumulator() = PrimitiveString::create(vm, value.typeof()); + interpreter.set(dst(), TRY(typeof_variable(vm, interpreter.current_executable().get_identifier(m_identifier)))); return {}; } ThrowCompletionOr ToNumeric::execute_impl(Bytecode::Interpreter& interpreter) const { - interpreter.accumulator() = TRY(interpreter.accumulator().to_numeric(interpreter.vm())); + interpreter.set(dst(), TRY(interpreter.get(src()).to_numeric(interpreter.vm()))); return {}; } @@ -1342,103 +1372,115 @@ ThrowCompletionOr BlockDeclarationInstantiation::execute_impl(Bytecode::In return {}; } -ByteString Load::to_byte_string_impl(Bytecode::Executable const&) const +ByteString Mov::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("Load {}", m_src); + return ByteString::formatted("Mov {}, {}", + format_operand("dst"sv, m_dst, executable), + format_operand("src"sv, m_src, executable)); } -ByteString LoadImmediate::to_byte_string_impl(Bytecode::Executable const&) const +ByteString NewBigInt::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("LoadImmediate {}", m_value); + return ByteString::formatted("NewBigInt {}, {}", + format_operand("dst"sv, dst(), executable), + m_bigint.to_base_deprecated(10)); } -ByteString Store::to_byte_string_impl(Bytecode::Executable const&) const -{ - return ByteString::formatted("Store {}", m_dst); -} - -ByteString NewBigInt::to_byte_string_impl(Bytecode::Executable const&) const -{ - return ByteString::formatted("NewBigInt \"{}\"", m_bigint.to_base_deprecated(10)); -} - -ByteString NewArray::to_byte_string_impl(Bytecode::Executable const&) const +ByteString NewArray::to_byte_string_impl(Bytecode::Executable const& executable) const { StringBuilder builder; - builder.append("NewArray"sv); + builder.appendff("NewArray {}", format_operand("dst"sv, dst(), executable)); if (m_element_count != 0) { - builder.appendff(" [{}-{}]", m_elements[0], m_elements[1]); + builder.appendff(", [{}-{}]", format_operand("from"sv, m_elements[0], executable), format_operand("to"sv, m_elements[1], executable)); } return builder.to_byte_string(); } -ByteString NewPrimitiveArray::to_byte_string_impl(Bytecode::Executable const&) const +ByteString NewPrimitiveArray::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("NewPrimitiveArray {}"sv, m_values.span()); + return ByteString::formatted("NewPrimitiveArray {}, {}"sv, + format_operand("dst"sv, dst(), executable), + m_values.span()); } -ByteString Append::to_byte_string_impl(Bytecode::Executable const&) const +ByteString ArrayAppend::to_byte_string_impl(Bytecode::Executable const& executable) const { - if (m_is_spread) - return ByteString::formatted("Append lhs: **{}", m_lhs); - return ByteString::formatted("Append lhs: {}", m_lhs); + return ByteString::formatted("Append {}, {}{}", + format_operand("dst"sv, dst(), executable), + format_operand("src"sv, src(), executable), + m_is_spread ? " **"sv : ""sv); } -ByteString IteratorToArray::to_byte_string_impl(Bytecode::Executable const&) const +ByteString IteratorToArray::to_byte_string_impl(Bytecode::Executable const& executable) const { - return "IteratorToArray"; + return ByteString::formatted("IteratorToArray {}, {}", + format_operand("dst"sv, dst(), executable), + format_operand("iterator"sv, iterator(), executable)); } ByteString NewString::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("NewString {} (\"{}\")", m_string, executable.string_table->get(m_string)); + return ByteString::formatted("NewString {}, \"{}\"", + format_operand("dst"sv, dst(), executable), + executable.string_table->get(m_string)); } -ByteString NewObject::to_byte_string_impl(Bytecode::Executable const&) const +ByteString NewObject::to_byte_string_impl(Bytecode::Executable const& executable) const { - return "NewObject"; + return ByteString::formatted("NewObject {}", format_operand("dst"sv, dst(), executable)); } ByteString NewRegExp::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("NewRegExp source:{} (\"{}\") flags:{} (\"{}\")", m_source_index, executable.get_string(m_source_index), m_flags_index, executable.get_string(m_flags_index)); + return ByteString::formatted("NewRegExp {}, source:{} (\"{}\") flags:{} (\"{}\")", + format_operand("dst"sv, dst(), executable), + m_source_index, executable.get_string(m_source_index), m_flags_index, executable.get_string(m_flags_index)); } -ByteString CopyObjectExcludingProperties::to_byte_string_impl(Bytecode::Executable const&) const +ByteString CopyObjectExcludingProperties::to_byte_string_impl(Bytecode::Executable const& executable) const { StringBuilder builder; - builder.appendff("CopyObjectExcludingProperties from:{}", m_from_object); + builder.appendff("CopyObjectExcludingProperties {}, {}", + format_operand("dst"sv, dst(), executable), + format_operand("from"sv, m_from_object, executable)); if (m_excluded_names_count != 0) { builder.append(" excluding:["sv); - builder.join(", "sv, ReadonlySpan(m_excluded_names, m_excluded_names_count)); + for (size_t i = 0; i < m_excluded_names_count; ++i) { + if (i != 0) + builder.append(", "sv); + builder.append(format_operand("#"sv, m_excluded_names[i], executable)); + } builder.append(']'); } return builder.to_byte_string(); } -ByteString ConcatString::to_byte_string_impl(Bytecode::Executable const&) const +ByteString ConcatString::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("ConcatString {}", m_lhs); + return ByteString::formatted("ConcatString {}, {}", + format_operand("dst"sv, dst(), executable), + format_operand("src"sv, src(), executable)); } ByteString GetCalleeAndThisFromEnvironment::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("GetCalleeAndThisFromEnvironment {} -> callee: {}, this:{} ", executable.identifier_table->get(m_identifier), m_callee_reg, m_this_reg); + return ByteString::formatted("GetCalleeAndThisFromEnvironment {}, {} <- {}", + format_operand("callee"sv, m_callee, executable), + format_operand("this"sv, m_this_value, executable), + executable.identifier_table->get(m_identifier)); } ByteString GetVariable::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("GetVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier)); + return ByteString::formatted("GetVariable {}, {}", + format_operand("dst"sv, dst(), executable), + executable.identifier_table->get(m_identifier)); } ByteString GetGlobal::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("GetGlobal {} ({})", m_identifier, executable.identifier_table->get(m_identifier)); -} - -ByteString GetLocal::to_byte_string_impl(Bytecode::Executable const&) const -{ - return ByteString::formatted("GetLocal {}", m_index); + return ByteString::formatted("GetGlobal {}, {}", format_operand("dst"sv, dst(), executable), + executable.identifier_table->get(m_identifier)); } ByteString DeleteVariable::to_byte_string_impl(Bytecode::Executable const& executable) const @@ -1457,21 +1499,27 @@ ByteString CreateVariable::to_byte_string_impl(Bytecode::Executable const& execu return ByteString::formatted("CreateVariable env:{} immutable:{} global:{} {} ({})", mode_string, m_is_immutable, m_is_global, m_identifier, executable.identifier_table->get(m_identifier)); } -ByteString EnterObjectEnvironment::to_byte_string_impl(Executable const&) const +ByteString EnterObjectEnvironment::to_byte_string_impl(Executable const& executable) const { - return ByteString::formatted("EnterObjectEnvironment"); + return ByteString::formatted("EnterObjectEnvironment {}", + format_operand("object"sv, m_object, executable)); } ByteString SetVariable::to_byte_string_impl(Bytecode::Executable const& executable) const { auto initialization_mode_name = m_initialization_mode == InitializationMode::Initialize ? "Initialize" : "Set"; auto mode_string = m_mode == EnvironmentMode::Lexical ? "Lexical" : "Variable"; - return ByteString::formatted("SetVariable env:{} init:{} {} ({})", mode_string, initialization_mode_name, m_identifier, executable.identifier_table->get(m_identifier)); + return ByteString::formatted("SetVariable {}, {}, env:{} init:{}", + executable.identifier_table->get(m_identifier), + format_operand("src"sv, src(), executable), + mode_string, initialization_mode_name); } -ByteString SetLocal::to_byte_string_impl(Bytecode::Executable const&) const +ByteString SetLocal::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("SetLocal {}", m_index); + return ByteString::formatted("SetLocal {}, {}", + format_operand("dst"sv, dst(), executable), + format_operand("src"sv, src(), executable)); } static StringView property_kind_to_string(PropertyKind kind) @@ -1496,49 +1544,83 @@ static StringView property_kind_to_string(PropertyKind kind) ByteString PutById::to_byte_string_impl(Bytecode::Executable const& executable) const { auto kind = property_kind_to_string(m_kind); - return ByteString::formatted("PutById kind:{} base:{}, property:{} ({})", kind, m_base, m_property, executable.identifier_table->get(m_property)); + return ByteString::formatted("PutById {}, {}, {}, kind:{}", + format_operand("base"sv, m_base, executable), + executable.identifier_table->get(m_property), + format_operand("src"sv, m_src, executable), + kind); } ByteString PutByIdWithThis::to_byte_string_impl(Bytecode::Executable const& executable) const { auto kind = property_kind_to_string(m_kind); - return ByteString::formatted("PutByIdWithThis kind:{} base:{}, property:{} ({}) this_value:{}", kind, m_base, m_property, executable.identifier_table->get(m_property), m_this_value); + return ByteString::formatted("PutByIdWithThis {}, {}, {}, {}, kind:{}", + format_operand("base"sv, m_base, executable), + executable.identifier_table->get(m_property), + format_operand("src"sv, m_src, executable), + format_operand("this"sv, m_this_value, executable), + kind); } ByteString PutPrivateById::to_byte_string_impl(Bytecode::Executable const& executable) const { auto kind = property_kind_to_string(m_kind); - return ByteString::formatted("PutPrivateById kind:{} base:{}, property:{} ({})", kind, m_base, m_property, executable.identifier_table->get(m_property)); + return ByteString::formatted( + "PutPrivateById {}, {}, {}, kind:{} ", + format_operand("base"sv, m_base, executable), + executable.identifier_table->get(m_property), + format_operand("src"sv, m_src, executable), + kind); } ByteString GetById::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("GetById {} ({})", m_property, executable.identifier_table->get(m_property)); + return ByteString::formatted("GetById {}, {}, {}", + format_operand("dst"sv, m_dst, executable), + format_operand("base"sv, m_base, executable), + executable.identifier_table->get(m_property)); } ByteString GetByIdWithThis::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("GetByIdWithThis {} ({}) this_value:{}", m_property, executable.identifier_table->get(m_property), m_this_value); + return ByteString::formatted("GetByIdWithThis {}, {}, {}, {}", + format_operand("dst"sv, m_dst, executable), + format_operand("base"sv, m_base, executable), + executable.identifier_table->get(m_property), + format_operand("this"sv, m_this_value, executable)); } ByteString GetPrivateById::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("GetPrivateById {} ({})", m_property, executable.identifier_table->get(m_property)); + return ByteString::formatted("GetPrivateById {}, {}, {}", + format_operand("dst"sv, m_dst, executable), + format_operand("base"sv, m_base, executable), + executable.identifier_table->get(m_property)); } ByteString HasPrivateId::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("HasPrivateId {} ({})", m_property, executable.identifier_table->get(m_property)); + return ByteString::formatted("HasPrivateId {}, {}, {}", + format_operand("dst"sv, m_dst, executable), + format_operand("base"sv, m_base, executable), + executable.identifier_table->get(m_property)); } ByteString DeleteById::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("DeleteById {} ({})", m_property, executable.identifier_table->get(m_property)); + return ByteString::formatted("DeleteById {}, {}, {}", + format_operand("dst"sv, m_dst, executable), + format_operand("base"sv, m_base, executable), + executable.identifier_table->get(m_property)); } ByteString DeleteByIdWithThis::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("DeleteByIdWithThis {} ({}) this_value:{}", m_property, executable.identifier_table->get(m_property), m_this_value); + return ByteString::formatted("DeleteByIdWithThis {}, {}, {}, {}", + format_operand("dst"sv, m_dst, executable), + format_operand("base"sv, m_base, executable), + executable.identifier_table->get(m_property), + format_operand("this"sv, m_this_value, executable)); } ByteString Jump::to_byte_string_impl(Bytecode::Executable const&) const @@ -1548,25 +1630,31 @@ ByteString Jump::to_byte_string_impl(Bytecode::Executable const&) const return ByteString::formatted("Jump "); } -ByteString JumpConditional::to_byte_string_impl(Bytecode::Executable const&) const +ByteString JumpIf::to_byte_string_impl(Bytecode::Executable const& executable) const { auto true_string = m_true_target.has_value() ? ByteString::formatted("{}", *m_true_target) : ""; auto false_string = m_false_target.has_value() ? ByteString::formatted("{}", *m_false_target) : ""; - return ByteString::formatted("JumpConditional true:{} false:{}", true_string, false_string); + return ByteString::formatted("JumpIf {}, \033[32mtrue\033[0m:{} \033[32mfalse\033[0m:{}", + format_operand("condition"sv, m_condition, executable), + true_string, false_string); } -ByteString JumpNullish::to_byte_string_impl(Bytecode::Executable const&) const +ByteString JumpNullish::to_byte_string_impl(Bytecode::Executable const& executable) const { auto true_string = m_true_target.has_value() ? ByteString::formatted("{}", *m_true_target) : ""; auto false_string = m_false_target.has_value() ? ByteString::formatted("{}", *m_false_target) : ""; - return ByteString::formatted("JumpNullish null:{} nonnull:{}", true_string, false_string); + return ByteString::formatted("JumpNullish {}, null:{} nonnull:{}", + format_operand("condition"sv, m_condition, executable), + true_string, false_string); } -ByteString JumpUndefined::to_byte_string_impl(Bytecode::Executable const&) const +ByteString JumpUndefined::to_byte_string_impl(Bytecode::Executable const& executable) const { auto true_string = m_true_target.has_value() ? ByteString::formatted("{}", *m_true_target) : ""; auto false_string = m_false_target.has_value() ? ByteString::formatted("{}", *m_false_target) : ""; - return ByteString::formatted("JumpUndefined undefined:{} not undefined:{}", true_string, false_string); + return ByteString::formatted("JumpUndefined {}, undefined:{} defined:{}", + format_operand("condition"sv, m_condition, executable), + true_string, false_string); } static StringView call_type_to_string(CallType type) @@ -1585,57 +1673,94 @@ static StringView call_type_to_string(CallType type) ByteString Call::to_byte_string_impl(Bytecode::Executable const& executable) const { auto type = call_type_to_string(m_type); - if (m_builtin.has_value()) - return ByteString::formatted("Call{} callee:{}, this:{}, first_arg:{} (builtin {})", type, m_callee, m_this_value, m_first_argument, m_builtin.value()); - if (m_expression_string.has_value()) - return ByteString::formatted("Call{} callee:{}, this:{}, first_arg:{} ({})", type, m_callee, m_this_value, m_first_argument, executable.get_string(m_expression_string.value())); - return ByteString::formatted("Call{} callee:{}, this:{}, first_arg:{}", type, m_callee, m_first_argument, m_this_value); + if (m_builtin.has_value()) { + return ByteString::formatted("Call{} {}, {}, {}, first_arg:{} (builtin {})", + type, + format_operand("dst"sv, m_dst, executable), + format_operand("callee"sv, m_callee, executable), + format_operand("this"sv, m_this_value, executable), + m_first_argument, + m_builtin.value()); + } + if (m_expression_string.has_value()) { + return ByteString::formatted("Call{} {}, {}, {}, first_arg:{} ({})", + type, + format_operand("dst"sv, m_dst, executable), + format_operand("callee"sv, m_callee, executable), + format_operand("this"sv, m_this_value, executable), + m_first_argument, + executable.get_string(m_expression_string.value())); + } + return ByteString::formatted("Call{} {}, {}, {}, first_arg:{}", + type, + format_operand("dst"sv, m_dst, executable), + format_operand("callee"sv, m_callee, executable), + format_operand("this"sv, m_this_value, executable), + m_first_argument); } ByteString CallWithArgumentArray::to_byte_string_impl(Bytecode::Executable const& executable) const { auto type = call_type_to_string(m_type); + StringBuilder builder; + builder.appendff("CallWithArgumentArray{} {}, {}, {}, {}", + type, + format_operand("dst"sv, m_dst, executable), + format_operand("callee"sv, m_callee, executable), + format_operand("this"sv, m_this_value, executable), + format_operand("arguments"sv, m_arguments, executable)); + if (m_expression_string.has_value()) - return ByteString::formatted("CallWithArgumentArray{} callee:{}, this:{}, arguments:[...acc] ({})", type, m_callee, m_this_value, executable.get_string(m_expression_string.value())); - return ByteString::formatted("CallWithArgumentArray{} callee:{}, this:{}, arguments:[...acc]", type, m_callee, m_this_value); + builder.appendff(" ({})", executable.get_string(m_expression_string.value())); + return builder.to_byte_string(); } -ByteString SuperCallWithArgumentArray::to_byte_string_impl(Bytecode::Executable const&) const +ByteString SuperCallWithArgumentArray::to_byte_string_impl(Bytecode::Executable const& executable) const { - return "SuperCallWithArgumentArray arguments:[...acc]"sv; + return ByteString::formatted("SuperCallWithArgumentArray {}, {}", + format_operand("dst"sv, m_dst, executable), + format_operand("arguments"sv, m_arguments, executable)); } -ByteString NewFunction::to_byte_string_impl(Bytecode::Executable const&) const +ByteString NewFunction::to_byte_string_impl(Bytecode::Executable const& executable) const { StringBuilder builder; - builder.append("NewFunction"sv); + builder.appendff("NewFunction {}", + format_operand("dst"sv, m_dst, executable)); 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()); + builder.appendff(" lhs_name:{}"sv, executable.get_identifier(m_lhs_name.value())); if (m_home_object.has_value()) - builder.appendff(" home_object:{}"sv, m_home_object.value()); + builder.appendff(", {}"sv, format_operand("home_object"sv, m_home_object.value(), executable)); return builder.to_byte_string(); } -ByteString NewClass::to_byte_string_impl(Bytecode::Executable const&) const +ByteString NewClass::to_byte_string_impl(Bytecode::Executable const& executable) const { StringBuilder builder; auto name = m_class_expression.name(); - builder.appendff("NewClass '{}'"sv, name.is_null() ? ""sv : name); + builder.appendff("NewClass {}", + format_operand("dst"sv, m_dst, executable)); + if (m_super_class.has_value()) + builder.appendff(", {}", format_operand("super_class"sv, *m_super_class, executable)); + if (!name.is_empty()) + builder.appendff(", {}", name); if (m_lhs_name.has_value()) - builder.appendff(" lhs_name:{}"sv, m_lhs_name.value()); + builder.appendff(", lhs_name:{}"sv, m_lhs_name.value()); return builder.to_byte_string(); } -ByteString Return::to_byte_string_impl(Bytecode::Executable const&) const +ByteString Return::to_byte_string_impl(Bytecode::Executable const& executable) const { + if (m_value.has_value()) + return ByteString::formatted("Return {}", format_operand("value"sv, m_value.value(), executable)); return "Return"; } -ByteString Increment::to_byte_string_impl(Bytecode::Executable const&) const +ByteString Increment::to_byte_string_impl(Bytecode::Executable const& executable) const { - return "Increment"; + return ByteString::formatted("Increment {}", format_operand("dst"sv, m_dst, executable)); } ByteString Decrement::to_byte_string_impl(Bytecode::Executable const&) const @@ -1643,19 +1768,28 @@ ByteString Decrement::to_byte_string_impl(Bytecode::Executable const&) const return "Decrement"; } -ByteString Throw::to_byte_string_impl(Bytecode::Executable const&) const +ByteString Throw::to_byte_string_impl(Bytecode::Executable const& executable) const { - return "Throw"; + return ByteString::formatted("Throw {}", + format_operand("src"sv, m_src, executable)); } -ByteString ThrowIfNotObject::to_byte_string_impl(Bytecode::Executable const&) const +ByteString ThrowIfNotObject::to_byte_string_impl(Bytecode::Executable const& executable) const { - return "ThrowIfNotObject"; + return ByteString::formatted("ThrowIfNotObject {}", + format_operand("src"sv, m_src, executable)); } -ByteString ThrowIfNullish::to_byte_string_impl(Bytecode::Executable const&) const +ByteString ThrowIfNullish::to_byte_string_impl(Bytecode::Executable const& executable) const { - return "ThrowIfNullish"; + return ByteString::formatted("ThrowIfNullish {}", + format_operand("src"sv, m_src, executable)); +} + +ByteString ThrowIfTDZ::to_byte_string_impl(Bytecode::Executable const& executable) const +{ + return ByteString::formatted("ThrowIfTDZ {}", + format_operand("src"sv, m_src, executable)); } ByteString EnterUnwindContext::to_byte_string_impl(Bytecode::Executable const&) const @@ -1683,122 +1817,168 @@ ByteString ContinuePendingUnwind::to_byte_string_impl(Bytecode::Executable const return ByteString::formatted("ContinuePendingUnwind resume:{}", m_resume_target); } -ByteString Yield::to_byte_string_impl(Bytecode::Executable const&) const +ByteString Yield::to_byte_string_impl(Bytecode::Executable const& executable) const { - if (m_continuation_label.has_value()) - return ByteString::formatted("Yield continuation:@{}", m_continuation_label->block().name()); - return ByteString::formatted("Yield return"); + if (m_continuation_label.has_value()) { + return ByteString::formatted("Yield continuation:@{}, {}", + m_continuation_label->block().name(), + format_operand("value"sv, m_value, executable)); + } + return ByteString::formatted("Yield return {}", + format_operand("value"sv, m_value, executable)); } -ByteString Await::to_byte_string_impl(Bytecode::Executable const&) const +ByteString Await::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("Await continuation:@{}", m_continuation_label.block().name()); + return ByteString::formatted("Await {}, continuation:@{}", + format_operand("argument"sv, m_argument, executable), + m_continuation_label.block().name()); } -ByteString GetByValue::to_byte_string_impl(Bytecode::Executable const&) const +ByteString GetByValue::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("GetByValue base:{}", m_base); + return ByteString::formatted("GetByValue {}, {}, {}", + format_operand("dst"sv, m_dst, executable), + format_operand("base"sv, m_base, executable), + format_operand("property"sv, m_property, executable)); } -ByteString GetByValueWithThis::to_byte_string_impl(Bytecode::Executable const&) const +ByteString GetByValueWithThis::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("GetByValueWithThis base:{} this_value:{}", m_base, m_this_value); + return ByteString::formatted("GetByValueWithThis {}, {}, {}", + format_operand("dst"sv, m_dst, executable), + format_operand("base"sv, m_base, executable), + format_operand("property"sv, m_property, executable)); } -ByteString PutByValue::to_byte_string_impl(Bytecode::Executable const&) const +ByteString PutByValue::to_byte_string_impl(Bytecode::Executable const& executable) const { auto kind = property_kind_to_string(m_kind); - return ByteString::formatted("PutByValue kind:{} base:{}, property:{}", kind, m_base, m_property); + return ByteString::formatted("PutByValue {}, {}, {}, kind:{}", + format_operand("base"sv, m_base, executable), + format_operand("property"sv, m_property, executable), + format_operand("src"sv, m_src, executable), + kind); } -ByteString PutByValueWithThis::to_byte_string_impl(Bytecode::Executable const&) const +ByteString PutByValueWithThis::to_byte_string_impl(Bytecode::Executable const& executable) const { auto kind = property_kind_to_string(m_kind); - return ByteString::formatted("PutByValueWithThis kind:{} base:{}, property:{} this_value:{}", kind, m_base, m_property, m_this_value); + return ByteString::formatted("PutByValueWithThis {}, {}, {}, {}, kind:{}", + format_operand("base"sv, m_base, executable), + format_operand("property"sv, m_property, executable), + format_operand("src"sv, m_src, executable), + format_operand("this"sv, m_this_value, executable), + kind); } -ByteString DeleteByValue::to_byte_string_impl(Bytecode::Executable const&) const +ByteString DeleteByValue::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("DeleteByValue base:{}", m_base); + return ByteString::formatted("DeleteByValue {}, {}, {}", + format_operand("dst"sv, dst(), executable), + format_operand("base"sv, m_base, executable), + format_operand("property"sv, m_property, executable)); } -ByteString DeleteByValueWithThis::to_byte_string_impl(Bytecode::Executable const&) const +ByteString DeleteByValueWithThis::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("DeleteByValueWithThis base:{} this_value:{}", m_base, m_this_value); + return ByteString::formatted("DeleteByValueWithThis {}, {}, {}, {}", + format_operand("dst"sv, dst(), executable), + format_operand("base"sv, m_base, executable), + format_operand("property"sv, m_property, executable), + format_operand("this"sv, m_this_value, executable)); } -ByteString GetIterator::to_byte_string_impl(Executable const&) const +ByteString GetIterator::to_byte_string_impl(Executable const& executable) const { auto hint = m_hint == IteratorHint::Sync ? "sync" : "async"; - return ByteString::formatted("GetIterator hint:{}", hint); + return ByteString::formatted("GetIterator {}, {}, hint:{}", + format_operand("dst"sv, m_dst, executable), + format_operand("iterable"sv, m_iterable, executable), + hint); } ByteString GetMethod::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("GetMethod {} ({})", m_property, executable.identifier_table->get(m_property)); + return ByteString::formatted("GetMethod {}, {}, {}", + format_operand("dst"sv, m_dst, executable), + format_operand("object"sv, m_object, executable), + executable.identifier_table->get(m_property)); } -ByteString GetObjectPropertyIterator::to_byte_string_impl(Bytecode::Executable const&) const +ByteString GetObjectPropertyIterator::to_byte_string_impl(Bytecode::Executable const& executable) const { - return "GetObjectPropertyIterator"; + return ByteString::formatted("GetObjectPropertyIterator {}, {}", + format_operand("dst"sv, dst(), executable), + format_operand("object"sv, object(), executable)); } -ByteString IteratorClose::to_byte_string_impl(Bytecode::Executable const&) const +ByteString IteratorClose::to_byte_string_impl(Bytecode::Executable const& executable) const { if (!m_completion_value.has_value()) - return ByteString::formatted("IteratorClose completion_type={} completion_value=", to_underlying(m_completion_type)); + return ByteString::formatted("IteratorClose {}, completion_type={} completion_value=", + format_operand("iterator_record"sv, m_iterator_record, executable), + to_underlying(m_completion_type)); auto completion_value_string = m_completion_value->to_string_without_side_effects(); - return ByteString::formatted("IteratorClose completion_type={} completion_value={}", to_underlying(m_completion_type), completion_value_string); + return ByteString::formatted("IteratorClose {}, completion_type={} completion_value={}", + format_operand("iterator_record"sv, m_iterator_record, executable), + to_underlying(m_completion_type), completion_value_string); } -ByteString AsyncIteratorClose::to_byte_string_impl(Bytecode::Executable const&) const +ByteString AsyncIteratorClose::to_byte_string_impl(Bytecode::Executable const& executable) const { - if (!m_completion_value.has_value()) - return ByteString::formatted("AsyncIteratorClose completion_type={} completion_value=", to_underlying(m_completion_type)); + if (!m_completion_value.has_value()) { + return ByteString::formatted("AsyncIteratorClose {}, completion_type:{} completion_value:", + format_operand("iterator_record"sv, m_iterator_record, executable), + to_underlying(m_completion_type)); + } - auto completion_value_string = m_completion_value->to_string_without_side_effects(); - return ByteString::formatted("AsyncIteratorClose completion_type={} completion_value={}", to_underlying(m_completion_type), completion_value_string); + return ByteString::formatted("AsyncIteratorClose {}, completion_type:{}, completion_value:{}", + format_operand("iterator_record"sv, m_iterator_record, executable), + to_underlying(m_completion_type), m_completion_value); } -ByteString IteratorNext::to_byte_string_impl(Executable const&) const +ByteString IteratorNext::to_byte_string_impl(Executable const& executable) const { - return "IteratorNext"; + return ByteString::formatted("IteratorNext {}, {}", + format_operand("dst"sv, m_dst, executable), + format_operand("iterator_record"sv, m_iterator_record, executable)); } -ByteString ResolveThisBinding::to_byte_string_impl(Bytecode::Executable const&) const +ByteString ResolveThisBinding::to_byte_string_impl(Bytecode::Executable const& executable) const { - return "ResolveThisBinding"sv; + return ByteString::formatted("ResolveThisBinding {}", format_operand("dst"sv, m_dst, executable)); } -ByteString ResolveSuperBase::to_byte_string_impl(Bytecode::Executable const&) const +ByteString ResolveSuperBase::to_byte_string_impl(Bytecode::Executable const& executable) const { - return "ResolveSuperBase"sv; + return ByteString::formatted("ResolveSuperBase {}", + format_operand("dst"sv, m_dst, executable)); } -ByteString GetNewTarget::to_byte_string_impl(Bytecode::Executable const&) const +ByteString GetNewTarget::to_byte_string_impl(Bytecode::Executable const& executable) const { - return "GetNewTarget"sv; + return ByteString::formatted("GetNewTarget {}", format_operand("dst"sv, m_dst, executable)); } -ByteString GetImportMeta::to_byte_string_impl(Bytecode::Executable const&) const +ByteString GetImportMeta::to_byte_string_impl(Bytecode::Executable const& executable) const { - return "GetImportMeta"sv; + return ByteString::formatted("GetImportMeta {}", format_operand("dst"sv, m_dst, executable)); } ByteString TypeofVariable::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("TypeofVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier)); + return ByteString::formatted("TypeofVariable {}, {}", + format_operand("dst"sv, m_dst, executable), + executable.identifier_table->get(m_identifier)); } -ByteString TypeofLocal::to_byte_string_impl(Bytecode::Executable const&) const +ByteString ToNumeric::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("TypeofLocal {}", m_index); -} - -ByteString ToNumeric::to_byte_string_impl(Bytecode::Executable const&) const -{ - return "ToNumeric"sv; + return ByteString::formatted("ToNumeric {}, {}", + format_operand("dst"sv, m_dst, executable), + format_operand("src"sv, m_src, executable)); } ByteString BlockDeclarationInstantiation::to_byte_string_impl(Bytecode::Executable const&) const @@ -1806,24 +1986,43 @@ ByteString BlockDeclarationInstantiation::to_byte_string_impl(Bytecode::Executab return "BlockDeclarationInstantiation"sv; } -ByteString ImportCall::to_byte_string_impl(Bytecode::Executable const&) const +ByteString ImportCall::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("ImportCall specifier:{} options:{}"sv, m_specifier, m_options); + return ByteString::formatted("ImportCall {}, {}, {}", + format_operand("dst"sv, m_dst, executable), + format_operand("specifier"sv, m_specifier, executable), + format_operand("options"sv, m_options, executable)); } -ByteString Catch::to_byte_string_impl(Bytecode::Executable const&) const +ByteString Catch::to_byte_string_impl(Bytecode::Executable const& executable) const { - return "Catch"sv; + return ByteString::formatted("Catch {}", + format_operand("dst"sv, m_dst, executable)); } -ByteString GetObjectFromIteratorRecord::to_byte_string_impl(Bytecode::Executable const&) const +ByteString GetObjectFromIteratorRecord::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("GetObjectFromIteratorRecord object:{} <- iterator_record:{}", m_object, m_iterator_record); + return ByteString::formatted("GetObjectFromIteratorRecord {}, {}", + format_operand("object"sv, m_object, executable), + format_operand("iterator_record"sv, m_iterator_record, executable)); } -ByteString GetNextMethodFromIteratorRecord::to_byte_string_impl(Bytecode::Executable const&) const +ByteString GetNextMethodFromIteratorRecord::to_byte_string_impl(Bytecode::Executable const& executable) const { - return ByteString::formatted("GetNextMethodFromIteratorRecord next_method:{} <- iterator_record:{}", m_next_method, m_iterator_record); + return ByteString::formatted("GetNextMethodFromIteratorRecord {}, {}", + format_operand("next_method"sv, m_next_method, executable), + format_operand("iterator_record"sv, m_iterator_record, executable)); +} + +ByteString End::to_byte_string_impl(Bytecode::Executable const& executable) const +{ + return ByteString::formatted("End {}", format_operand("value"sv, m_value, executable)); +} + +ByteString Dump::to_byte_string_impl(Bytecode::Executable const& executable) const +{ + return ByteString::formatted("Dump '{}', {}", m_text, + format_operand("value"sv, m_value, executable)); } } diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.h b/Userland/Libraries/LibJS/Bytecode/Interpreter.h index 595cd3437d..0ef2185eaf 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.h +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.h @@ -89,7 +89,7 @@ public: void enter_unwind_context(); void leave_unwind_context(); - void catch_exception(); + void catch_exception(Operand dst); void enter_object_environment(Object&); @@ -130,6 +130,6 @@ private: extern bool g_dump_bytecode; -ThrowCompletionOr> compile(VM&, ASTNode const& no, JS::FunctionKind kind, DeprecatedFlyString const& name); +ThrowCompletionOr> compile(VM&, ASTNode const&, ReadonlySpan, JS::FunctionKind kind, DeprecatedFlyString const& name); } diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index effd121605..3843964d64 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -30,10 +31,11 @@ class FunctionExpression; namespace JS::Bytecode::Op { -class Load final : public Instruction { +class Mov final : public Instruction { public: - explicit Load(Register src) - : Instruction(Type::Load, sizeof(*this)) + Mov(Operand dst, Operand src) + : Instruction(Type::Mov, sizeof(*this)) + , m_dst(dst) , m_src(src) { } @@ -41,44 +43,12 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; - Register src() const { return m_src; } + Operand dst() const { return m_dst; } + Operand src() const { return m_src; } private: - Register m_src; -}; - -class LoadImmediate final : public Instruction { -public: - explicit LoadImmediate(Value value) - : Instruction(Type::LoadImmediate, sizeof(*this)) - , m_value(value) - { - } - - ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; - ByteString to_byte_string_impl(Bytecode::Executable const&) const; - - Value value() const { return m_value; } - -private: - Value m_value; -}; - -class Store final : public Instruction { -public: - explicit Store(Register dst) - : Instruction(Type::Store, sizeof(*this)) - , m_dst(dst) - { - } - - ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; - ByteString to_byte_string_impl(Bytecode::Executable const&) const; - - Register dst() const { return m_dst; } - -private: - Register m_dst; + Operand m_dst; + Operand m_src; }; #define JS_ENUMERATE_COMMON_BINARY_OPS(O) \ @@ -108,19 +78,25 @@ private: #define JS_DECLARE_COMMON_BINARY_OP(OpTitleCase, op_snake_case) \ class OpTitleCase final : public Instruction { \ public: \ - explicit OpTitleCase(Register lhs_reg) \ + explicit OpTitleCase(Operand dst, Operand lhs, Operand rhs) \ : Instruction(Type::OpTitleCase, sizeof(*this)) \ - , m_lhs_reg(lhs_reg) \ + , m_dst(dst) \ + , m_lhs(lhs) \ + , m_rhs(rhs) \ { \ } \ \ ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; \ ByteString to_byte_string_impl(Bytecode::Executable const&) const; \ \ - Register lhs() const { return m_lhs_reg; } \ + Operand dst() const { return m_dst; } \ + Operand lhs() const { return m_lhs; } \ + Operand rhs() const { return m_rhs; } \ \ private: \ - Register m_lhs_reg; \ + Operand m_dst; \ + Operand m_lhs; \ + Operand m_rhs; \ }; JS_ENUMERATE_COMMON_BINARY_OPS(JS_DECLARE_COMMON_BINARY_OP) @@ -136,13 +112,22 @@ JS_ENUMERATE_COMMON_BINARY_OPS(JS_DECLARE_COMMON_BINARY_OP) #define JS_DECLARE_COMMON_UNARY_OP(OpTitleCase, op_snake_case) \ class OpTitleCase final : public Instruction { \ public: \ - OpTitleCase() \ + OpTitleCase(Operand dst, Operand src) \ : Instruction(Type::OpTitleCase, sizeof(*this)) \ + , m_dst(dst) \ + , m_src(src) \ { \ } \ \ ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; \ ByteString to_byte_string_impl(Bytecode::Executable const&) const; \ + \ + Operand dst() const { return m_dst; } \ + Operand src() const { return m_src; } \ + \ + private: \ + Operand m_dst; \ + Operand m_src; \ }; JS_ENUMERATE_COMMON_UNARY_OPS(JS_DECLARE_COMMON_UNARY_OP) @@ -150,8 +135,9 @@ JS_ENUMERATE_COMMON_UNARY_OPS(JS_DECLARE_COMMON_UNARY_OP) class NewString final : public Instruction { public: - explicit NewString(StringTableIndex string) + NewString(Operand dst, StringTableIndex string) : Instruction(Type::NewString, sizeof(*this)) + , m_dst(dst) , m_string(string) { } @@ -159,27 +145,36 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + Operand dst() const { return m_dst; } StringTableIndex index() const { return m_string; } private: + Operand m_dst; StringTableIndex m_string; }; class NewObject final : public Instruction { public: - NewObject() + explicit NewObject(Operand dst) : Instruction(Type::NewObject, sizeof(*this)) + , m_dst(dst) { } ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + + Operand dst() const { return m_dst; } + +private: + Operand m_dst; }; class NewRegExp final : public Instruction { public: - NewRegExp(StringTableIndex source_index, StringTableIndex flags_index, RegexTableIndex regex_index) + NewRegExp(Operand dst, StringTableIndex source_index, StringTableIndex flags_index, RegexTableIndex regex_index) : Instruction(Type::NewRegExp, sizeof(*this)) + , m_dst(dst) , m_source_index(source_index) , m_flags_index(flags_index) , m_regex_index(regex_index) @@ -189,11 +184,13 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + Operand dst() const { return m_dst; } StringTableIndex source_index() const { return m_source_index; } StringTableIndex flags_index() const { return m_flags_index; } RegexTableIndex regex_index() const { return m_regex_index; } private: + Operand m_dst; StringTableIndex m_source_index; StringTableIndex m_flags_index; RegexTableIndex m_regex_index; @@ -205,8 +202,9 @@ private: #define JS_DECLARE_NEW_BUILTIN_ERROR_OP(ErrorName) \ class New##ErrorName final : public Instruction { \ public: \ - explicit New##ErrorName(StringTableIndex error_string) \ + New##ErrorName(Operand dst, StringTableIndex error_string) \ : Instruction(Type::New##ErrorName, sizeof(*this)) \ + , m_dst(dst) \ , m_error_string(error_string) \ { \ } \ @@ -214,9 +212,11 @@ private: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; \ ByteString to_byte_string_impl(Bytecode::Executable const&) const; \ \ + Operand dst() const { return m_dst; } \ StringTableIndex error_string() const { return m_error_string; } \ \ private: \ + Operand m_dst; \ StringTableIndex m_error_string; \ }; @@ -226,8 +226,9 @@ JS_ENUMERATE_NEW_BUILTIN_ERROR_OPS(JS_DECLARE_NEW_BUILTIN_ERROR_OP) // NOTE: This instruction is variable-width depending on the number of excluded names class CopyObjectExcludingProperties final : public Instruction { public: - CopyObjectExcludingProperties(Register from_object, Vector const& excluded_names) + CopyObjectExcludingProperties(Operand dst, Operand from_object, Vector const& excluded_names) : Instruction(Type::CopyObjectExcludingProperties, length_impl(excluded_names.size())) + , m_dst(dst) , m_from_object(from_object) , m_excluded_names_count(excluded_names.size()) { @@ -240,23 +241,26 @@ public: size_t length_impl(size_t excluded_names_count) const { - return round_up_to_power_of_two(alignof(void*), sizeof(*this) + sizeof(Register) * excluded_names_count); + return round_up_to_power_of_two(alignof(void*), sizeof(*this) + sizeof(Operand) * excluded_names_count); } - Register from_object() const { return m_from_object; } + Operand dst() const { return m_dst; } + Operand from_object() const { return m_from_object; } size_t excluded_names_count() const { return m_excluded_names_count; } - Register const* excluded_names() const { return m_excluded_names; } + Operand const* excluded_names() const { return m_excluded_names; } private: - Register m_from_object; + Operand m_dst; + Operand m_from_object; size_t m_excluded_names_count { 0 }; - Register m_excluded_names[]; + Operand m_excluded_names[]; }; class NewBigInt final : public Instruction { public: - explicit NewBigInt(Crypto::SignedBigInteger bigint) + NewBigInt(Operand dst, Crypto::SignedBigInteger bigint) : Instruction(Type::NewBigInt, sizeof(*this)) + , m_dst(dst) , m_bigint(move(bigint)) { } @@ -264,23 +268,27 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + Operand dst() const { return m_dst; } Crypto::SignedBigInteger const& bigint() const { return m_bigint; } private: + Operand m_dst; Crypto::SignedBigInteger m_bigint; }; // NOTE: This instruction is variable-width depending on the number of elements! class NewArray final : public Instruction { public: - NewArray() + explicit NewArray(Operand dst) : Instruction(Type::NewArray, length_impl(0)) + , m_dst(dst) , m_element_count(0) { } - explicit NewArray(AK::Array const& elements_range) + NewArray(Operand dst, AK::Array const& elements_range) : Instruction(Type::NewArray, length_impl(elements_range[1].index() - elements_range[0].index() + 1)) + , m_dst(dst) , m_element_count(elements_range[1].index() - elements_range[0].index() + 1) { m_elements[0] = elements_range[0]; @@ -290,18 +298,20 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + Operand dst() const { return m_dst; } + size_t length_impl(size_t element_count) const { - return round_up_to_power_of_two(alignof(void*), sizeof(*this) + sizeof(Register) * (element_count == 0 ? 0 : 2)); + return round_up_to_power_of_two(alignof(void*), sizeof(*this) + sizeof(Operand) * (element_count == 0 ? 0 : 2)); } - Register start() const + Operand start() const { VERIFY(m_element_count); return m_elements[0]; } - Register end() const + Operand end() const { VERIFY(m_element_count); return m_elements[1]; @@ -310,14 +320,16 @@ public: size_t element_count() const { return m_element_count; } private: + Operand m_dst; size_t m_element_count { 0 }; - Register m_elements[]; + Operand m_elements[]; }; class NewPrimitiveArray final : public Instruction { public: - explicit NewPrimitiveArray(FixedArray values) + NewPrimitiveArray(Operand dst, FixedArray values) : Instruction(Type::NewPrimitiveArray, sizeof(*this)) + , m_dst(dst) , m_values(move(values)) { } @@ -325,17 +337,20 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + Operand dst() const { return m_dst; } ReadonlySpan values() const { return m_values.span(); } private: + Operand m_dst; FixedArray m_values; }; -class Append final : public Instruction { +class ArrayAppend final : public Instruction { public: - Append(Register lhs, bool is_spread) - : Instruction(Type::Append, sizeof(*this)) - , m_lhs(lhs) + ArrayAppend(Operand dst, Operand src, bool is_spread) + : Instruction(Type::ArrayAppend, sizeof(*this)) + , m_dst(dst) + , m_src(src) , m_is_spread(is_spread) { } @@ -343,18 +358,21 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; - Register lhs() const { return m_lhs; } + Operand dst() const { return m_dst; } + Operand src() const { return m_src; } bool is_spread() const { return m_is_spread; } private: - Register m_lhs; + Operand m_dst; + Operand m_src; bool m_is_spread = false; }; class ImportCall final : public Instruction { public: - ImportCall(Register specifier, Register options) + ImportCall(Operand dst, Operand specifier, Operand options) : Instruction(Type::ImportCall, sizeof(*this)) + , m_dst(dst) , m_specifier(specifier) , m_options(options) { @@ -363,40 +381,54 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; - Register specifier() const { return m_specifier; } - Register options() const { return m_options; } + Operand dst() const { return m_dst; } + Operand specifier() const { return m_specifier; } + Operand options() const { return m_options; } private: - Register m_specifier; - Register m_options; + Operand m_dst; + Operand m_specifier; + Operand m_options; }; class IteratorToArray final : public Instruction { public: - IteratorToArray() + explicit IteratorToArray(Operand dst, Operand iterator) : Instruction(Type::IteratorToArray, sizeof(*this)) + , m_dst(dst) + , m_iterator(iterator) { } ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + + Operand dst() const { return m_dst; } + Operand iterator() const { return m_iterator; } + +private: + Operand m_dst; + Operand m_iterator; }; class ConcatString final : public Instruction { public: - explicit ConcatString(Register lhs) + explicit ConcatString(Operand dst, Operand src) : Instruction(Type::ConcatString, sizeof(*this)) - , m_lhs(lhs) + , m_dst(dst) + , m_src(src) { } ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; - Register lhs() const { return m_lhs; } + Operand dst() const { return m_dst; } + Operand src() const { return m_src; } private: - Register m_lhs; + Operand m_dst; + Operand m_src; }; enum class EnvironmentMode { @@ -417,24 +449,36 @@ public: class EnterObjectEnvironment final : public Instruction { public: - explicit EnterObjectEnvironment() + explicit EnterObjectEnvironment(Operand object) : Instruction(Type::EnterObjectEnvironment, sizeof(*this)) + , m_object(object) { } ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + + Operand object() const { return m_object; } + +private: + Operand m_object; }; class Catch final : public Instruction { public: - explicit Catch() + explicit Catch(Operand dst) : Instruction(Type::Catch, sizeof(*this)) + , m_dst(dst) { } ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + + Operand dst() const { return m_dst; } + +private: + Operand m_dst; }; class CreateVariable final : public Instruction { @@ -472,9 +516,10 @@ public: Initialize, Set, }; - explicit SetVariable(IdentifierTableIndex identifier, u32 cache_index, InitializationMode initialization_mode = InitializationMode::Set, EnvironmentMode mode = EnvironmentMode::Lexical) + explicit SetVariable(IdentifierTableIndex identifier, Operand src, u32 cache_index, InitializationMode initialization_mode = InitializationMode::Set, EnvironmentMode mode = EnvironmentMode::Lexical) : Instruction(Type::SetVariable, sizeof(*this)) , m_identifier(identifier) + , m_src(src) , m_mode(mode) , m_initialization_mode(initialization_mode) , m_cache_index(cache_index) @@ -485,12 +530,14 @@ public: ByteString to_byte_string_impl(Bytecode::Executable const&) const; IdentifierTableIndex identifier() const { return m_identifier; } + Operand src() const { return m_src; } EnvironmentMode mode() const { return m_mode; } InitializationMode initialization_mode() const { return m_initialization_mode; } u32 cache_index() const { return m_cache_index; } private: IdentifierTableIndex m_identifier; + Operand m_src; EnvironmentMode m_mode; InitializationMode m_initialization_mode { InitializationMode::Set }; u32 m_cache_index { 0 }; @@ -498,9 +545,10 @@ private: class SetLocal final : public Instruction { public: - explicit SetLocal(size_t index) + SetLocal(size_t index, Operand src) : Instruction(Type::SetLocal, sizeof(*this)) , m_index(index) + , m_src(src) { } @@ -508,18 +556,21 @@ public: ByteString to_byte_string_impl(Bytecode::Executable const&) const; size_t index() const { return m_index; } + Operand dst() const { return Operand(Operand::Type::Local, m_index); } + Operand src() const { return m_src; } private: size_t m_index; + Operand m_src; }; class GetCalleeAndThisFromEnvironment final : public Instruction { public: - explicit GetCalleeAndThisFromEnvironment(IdentifierTableIndex identifier, Register callee_reg, Register this_reg, u32 cache_index) + explicit GetCalleeAndThisFromEnvironment(Operand callee, Operand this_value, IdentifierTableIndex identifier, u32 cache_index) : Instruction(Type::GetCalleeAndThisFromEnvironment, sizeof(*this)) , m_identifier(identifier) - , m_callee_reg(callee_reg) - , m_this_reg(this_reg) + , m_callee(callee) + , m_this_value(this_value) , m_cache_index(cache_index) { } @@ -529,20 +580,21 @@ public: IdentifierTableIndex identifier() const { return m_identifier; } u32 cache_index() const { return m_cache_index; } - Register callee() const { return m_callee_reg; } - Register this_() const { return m_this_reg; } + Operand callee() const { return m_callee; } + Operand this_() const { return m_this_value; } private: IdentifierTableIndex m_identifier; - Register m_callee_reg; - Register m_this_reg; + Operand m_callee; + Operand m_this_value; u32 m_cache_index { 0 }; }; class GetVariable final : public Instruction { public: - explicit GetVariable(IdentifierTableIndex identifier, u32 cache_index) + explicit GetVariable(Operand dst, IdentifierTableIndex identifier, u32 cache_index) : Instruction(Type::GetVariable, sizeof(*this)) + , m_dst(dst) , m_identifier(identifier) , m_cache_index(cache_index) { @@ -551,18 +603,21 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + Operand dst() const { return m_dst; } IdentifierTableIndex identifier() const { return m_identifier; } u32 cache_index() const { return m_cache_index; } private: + Operand m_dst; IdentifierTableIndex m_identifier; u32 m_cache_index { 0 }; }; class GetGlobal final : public Instruction { public: - explicit GetGlobal(IdentifierTableIndex identifier, u32 cache_index) + GetGlobal(Operand dst, IdentifierTableIndex identifier, u32 cache_index) : Instruction(Type::GetGlobal, sizeof(*this)) + , m_dst(dst) , m_identifier(identifier) , m_cache_index(cache_index) { @@ -571,35 +626,21 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + Operand dst() const { return m_dst; } IdentifierTableIndex identifier() const { return m_identifier; } u32 cache_index() const { return m_cache_index; } private: + Operand m_dst; IdentifierTableIndex m_identifier; u32 m_cache_index { 0 }; }; -class GetLocal final : public Instruction { -public: - explicit GetLocal(size_t index) - : Instruction(Type::GetLocal, sizeof(*this)) - , m_index(index) - { - } - - ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; - ByteString to_byte_string_impl(Bytecode::Executable const&) const; - - size_t index() const { return m_index; } - -private: - size_t m_index; -}; - class DeleteVariable final : public Instruction { public: - explicit DeleteVariable(IdentifierTableIndex identifier) + explicit DeleteVariable(Operand dst, IdentifierTableIndex identifier) : Instruction(Type::DeleteVariable, sizeof(*this)) + , m_dst(dst) , m_identifier(identifier) { } @@ -607,16 +648,20 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + Operand dst() const { return m_dst; } IdentifierTableIndex identifier() const { return m_identifier; } private: + Operand m_dst; IdentifierTableIndex m_identifier; }; class GetById final : public Instruction { public: - GetById(IdentifierTableIndex property, u32 cache_index) + GetById(Operand dst, Operand base, IdentifierTableIndex property, u32 cache_index) : Instruction(Type::GetById, sizeof(*this)) + , m_dst(dst) + , m_base(base) , m_property(property) , m_cache_index(cache_index) { @@ -625,18 +670,24 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + Operand dst() const { return m_dst; } + Operand base() const { return m_base; } IdentifierTableIndex property() const { return m_property; } u32 cache_index() const { return m_cache_index; } private: + Operand m_dst; + Operand m_base; IdentifierTableIndex m_property; u32 m_cache_index { 0 }; }; class GetByIdWithThis final : public Instruction { public: - GetByIdWithThis(IdentifierTableIndex property, Register this_value, u32 cache_index) + GetByIdWithThis(Operand dst, Operand base, IdentifierTableIndex property, Operand this_value, u32 cache_index) : Instruction(Type::GetByIdWithThis, sizeof(*this)) + , m_dst(dst) + , m_base(base) , m_property(property) , m_this_value(this_value) , m_cache_index(cache_index) @@ -646,20 +697,26 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + Operand dst() const { return m_dst; } + Operand base() const { return m_base; } IdentifierTableIndex property() const { return m_property; } - Register this_value() const { return m_this_value; } + Operand this_value() const { return m_this_value; } u32 cache_index() const { return m_cache_index; } private: + Operand m_dst; + Operand m_base; IdentifierTableIndex m_property; - Register m_this_value; + Operand m_this_value; u32 m_cache_index { 0 }; }; class GetPrivateById final : public Instruction { public: - explicit GetPrivateById(IdentifierTableIndex property) + explicit GetPrivateById(Operand dst, Operand base, IdentifierTableIndex property) : Instruction(Type::GetPrivateById, sizeof(*this)) + , m_dst(dst) + , m_base(base) , m_property(property) { } @@ -667,16 +724,22 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + Operand dst() const { return m_dst; } + Operand base() const { return m_base; } IdentifierTableIndex property() const { return m_property; } private: + Operand m_dst; + Operand m_base; IdentifierTableIndex m_property; }; class HasPrivateId final : public Instruction { public: - explicit HasPrivateId(IdentifierTableIndex property) + HasPrivateId(Operand dst, Operand base, IdentifierTableIndex property) : Instruction(Type::HasPrivateId, sizeof(*this)) + , m_dst(dst) + , m_base(base) , m_property(property) { } @@ -684,9 +747,13 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + Operand dst() const { return m_dst; } + Operand base() const { return m_base; } IdentifierTableIndex property() const { return m_property; } private: + Operand m_dst; + Operand m_base; IdentifierTableIndex m_property; }; @@ -701,10 +768,11 @@ enum class PropertyKind { class PutById final : public Instruction { public: - explicit PutById(Register base, IdentifierTableIndex property, PropertyKind kind, u32 cache_index) + explicit PutById(Operand base, IdentifierTableIndex property, Operand src, PropertyKind kind, u32 cache_index) : Instruction(Type::PutById, sizeof(*this)) , m_base(base) , m_property(property) + , m_src(src) , m_kind(kind) , m_cache_index(cache_index) { @@ -713,25 +781,28 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; - Register base() const { return m_base; } + Operand base() const { return m_base; } IdentifierTableIndex property() const { return m_property; } + Operand src() const { return m_src; } PropertyKind kind() const { return m_kind; } u32 cache_index() const { return m_cache_index; } private: - Register m_base; + Operand m_base; IdentifierTableIndex m_property; + Operand m_src; PropertyKind m_kind; u32 m_cache_index { 0 }; }; class PutByIdWithThis final : public Instruction { public: - PutByIdWithThis(Register base, Register this_value, IdentifierTableIndex property, PropertyKind kind, u32 cache_index) + PutByIdWithThis(Operand base, Operand this_value, IdentifierTableIndex property, Operand src, PropertyKind kind, u32 cache_index) : Instruction(Type::PutByIdWithThis, sizeof(*this)) , m_base(base) , m_this_value(this_value) , m_property(property) + , m_src(src) , m_kind(kind) , m_cache_index(cache_index) { @@ -740,26 +811,29 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; - Register base() const { return m_base; } - Register this_value() const { return m_this_value; } + Operand base() const { return m_base; } + Operand this_value() const { return m_this_value; } IdentifierTableIndex property() const { return m_property; } + Operand src() const { return m_src; } PropertyKind kind() const { return m_kind; } u32 cache_index() const { return m_cache_index; } private: - Register m_base; - Register m_this_value; + Operand m_base; + Operand m_this_value; IdentifierTableIndex m_property; + Operand m_src; PropertyKind m_kind; u32 m_cache_index { 0 }; }; class PutPrivateById final : public Instruction { public: - explicit PutPrivateById(Register base, IdentifierTableIndex property, PropertyKind kind = PropertyKind::KeyValue) + explicit PutPrivateById(Operand base, IdentifierTableIndex property, Operand src, PropertyKind kind = PropertyKind::KeyValue) : Instruction(Type::PutPrivateById, sizeof(*this)) , m_base(base) , m_property(property) + , m_src(src) , m_kind(kind) { } @@ -767,19 +841,23 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; - Register base() const { return m_base; } + Operand base() const { return m_base; } IdentifierTableIndex property() const { return m_property; } + Operand src() const { return m_src; } private: - Register m_base; + Operand m_base; IdentifierTableIndex m_property; + Operand m_src; PropertyKind m_kind; }; class DeleteById final : public Instruction { public: - explicit DeleteById(IdentifierTableIndex property) + explicit DeleteById(Operand dst, Operand base, IdentifierTableIndex property) : Instruction(Type::DeleteById, sizeof(*this)) + , m_dst(dst) + , m_base(base) , m_property(property) { } @@ -787,16 +865,22 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; + Operand dst() const { return m_dst; } + Operand base() const { return m_base; } IdentifierTableIndex property() const { return m_property; } private: + Operand m_dst; + Operand m_base; IdentifierTableIndex m_property; }; class DeleteByIdWithThis final : public Instruction { public: - DeleteByIdWithThis(Register this_value, IdentifierTableIndex property) + DeleteByIdWithThis(Operand dst, Operand base, Operand this_value, IdentifierTableIndex property) : Instruction(Type::DeleteByIdWithThis, sizeof(*this)) + , m_dst(dst) + , m_base(base) , m_this_value(this_value) , m_property(property) { @@ -805,36 +889,48 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; - Register this_value() const { return m_this_value; } + Operand dst() const { return m_dst; } + Operand base() const { return m_base; } + Operand this_value() const { return m_this_value; } IdentifierTableIndex property() const { return m_property; } private: - Register m_this_value; + Operand m_dst; + Operand m_base; + Operand m_this_value; IdentifierTableIndex m_property; }; class GetByValue final : public Instruction { public: - explicit GetByValue(Register base) + explicit GetByValue(Operand dst, Operand base, Operand property) : Instruction(Type::GetByValue, sizeof(*this)) + , m_dst(dst) , m_base(base) + , m_property(property) { } ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; - Register base() const { return m_base; } + Operand dst() const { return m_dst; } + Operand base() const { return m_base; } + Operand property() const { return m_property; } private: - Register m_base; + Operand m_dst; + Operand m_base; + Operand m_property; }; class GetByValueWithThis final : public Instruction { public: - GetByValueWithThis(Register base, Register this_value) + GetByValueWithThis(Operand dst, Operand base, Operand property, Operand this_value) : Instruction(Type::GetByValueWithThis, sizeof(*this)) + , m_dst(dst) , m_base(base) + , m_property(property) , m_this_value(this_value) { } @@ -842,20 +938,25 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; - Register base() const { return m_base; } - Register this_value() const { return m_this_value; } + Operand dst() const { return m_dst; } + Operand base() const { return m_base; } + Operand property() const { return m_property; } + Operand this_value() const { return m_this_value; } private: - Register m_base; - Register m_this_value; + Operand m_dst; + Operand m_base; + Operand m_property; + Operand m_this_value; }; class PutByValue final : public Instruction { public: - PutByValue(Register base, Register property, PropertyKind kind = PropertyKind::KeyValue) + PutByValue(Operand base, Operand property, Operand src, PropertyKind kind = PropertyKind::KeyValue) : Instruction(Type::PutByValue, sizeof(*this)) , m_base(base) , m_property(property) + , m_src(src) , m_kind(kind) { } @@ -863,23 +964,26 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; - Register base() const { return m_base; } - Register property() const { return m_property; } + Operand base() const { return m_base; } + Operand property() const { return m_property; } + Operand src() const { return m_src; } PropertyKind kind() const { return m_kind; } private: - Register m_base; - Register m_property; + Operand m_base; + Operand m_property; + Operand m_src; PropertyKind m_kind; }; class PutByValueWithThis final : public Instruction { public: - PutByValueWithThis(Register base, Register property, Register this_value, PropertyKind kind = PropertyKind::KeyValue) + PutByValueWithThis(Operand base, Operand property, Operand this_value, Operand src, PropertyKind kind = PropertyKind::KeyValue) : Instruction(Type::PutByValueWithThis, sizeof(*this)) , m_base(base) , m_property(property) , m_this_value(this_value) + , m_src(src) , m_kind(kind) { } @@ -887,53 +991,67 @@ public: ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; - Register base() const { return m_base; } - Register property() const { return m_property; } - Register this_value() const { return m_this_value; } + Operand base() const { return m_base; } + Operand property() const { return m_property; } + Operand this_value() const { return m_this_value; } + Operand src() const { return m_src; } PropertyKind kind() const { return m_kind; } private: - Register m_base; - Register m_property; - Register m_this_value; + Operand m_base; + Operand m_property; + Operand m_this_value; + Operand m_src; PropertyKind m_kind; }; class DeleteByValue final : public Instruction { public: - DeleteByValue(Register base) + DeleteByValue(Operand dst, Operand base, Operand property) : Instruction(Type::DeleteByValue, sizeof(*this)) + , m_dst(dst) , m_base(base) + , m_property(property) { } ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; - Register base() const { return m_base; } + Operand dst() const { return m_dst; } + Operand base() const { return m_base; } + Operand property() const { return m_property; } private: - Register m_base; + Operand m_dst; + Operand m_base; + Operand m_property; }; class DeleteByValueWithThis final : public Instruction { public: - DeleteByValueWithThis(Register base, Register this_value) + DeleteByValueWithThis(Operand dst, Operand base, Operand this_value, Operand property) : Instruction(Type::DeleteByValueWithThis, sizeof(*this)) + , m_dst(dst) , m_base(base) , m_this_value(this_value) + , m_property(property) { } - Register base() const { return m_base; } - Register this_value() const { return m_this_value; } + Operand dst() const { return m_dst; } + Operand base() const { return m_base; } + Operand this_value() const { return m_this_value; } + Operand property() const { return m_property; } ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; ByteString to_byte_string_impl(Bytecode::Executable const&) const; private: - Register m_base; - Register m_this_value; + Operand m_dst; + Operand m_base; + Operand m_this_value; + Operand m_property; }; class Jump : public Instruction { @@ -947,6 +1065,13 @@ public: { } + explicit Jump(Type type, Label taken_target, Label nontaken_target, size_t sizeof_self) + : Instruction(type, sizeof_self) + , m_true_target(move(taken_target)) + , m_false_target(move(nontaken_target)) + { + } + explicit Jump(Label taken_target, Optional