From 4db2efaecb3c11fd4513fea0455aa105e099d6b2 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 9 Dec 2022 18:48:57 +0000 Subject: [PATCH] LibJS/Bytecode: Implement yield* --- .../Libraries/LibJS/Bytecode/ASTCodegen.cpp | 270 +++++++++++++++++- .../Libraries/LibJS/Bytecode/Instruction.h | 4 + Userland/Libraries/LibJS/Bytecode/Op.cpp | 58 ++++ Userland/Libraries/LibJS/Bytecode/Op.h | 77 +++++ Userland/Libraries/LibJS/Runtime/ErrorTypes.h | 1 + 5 files changed, 406 insertions(+), 4 deletions(-) diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 40030fea85..aadd742d4f 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace JS { @@ -1611,10 +1612,271 @@ Bytecode::CodeGenerationErrorOr YieldExpression::generate_bytecode(Bytecod }; if (m_is_yield_from) { - return Bytecode::CodeGenerationError { - this, - "Unimplemented form: `yield*`"sv, - }; + // 15.5.5 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-evaluation + // FIXME: 1. Let generatorKind be GetGeneratorKind(). + + // 2. Let exprRef be ? Evaluation of AssignmentExpression. + // 3. Let value be ? GetValue(exprRef). + VERIFY(m_argument); + TRY(m_argument->generate_bytecode(generator)); + + // 4. Let iteratorRecord be ? GetIterator(value, generatorKind). + // FIXME: Consider generatorKind. + auto iterator_record_register = generator.allocate_register(); + generator.emit(); + generator.emit(iterator_record_register); + + // 5. Let iterator be iteratorRecord.[[Iterator]]. + auto iterator_register = generator.allocate_register(); + auto iterator_identifier = generator.intern_identifier("iterator"); + generator.emit(iterator_identifier); + generator.emit(iterator_register); + + // Cache iteratorRecord.[[NextMethod]] for use in step 7.a.i. + auto next_method_register = generator.allocate_register(); + auto next_method_identifier = generator.intern_identifier("next"); + generator.emit(iterator_record_register); + generator.emit(next_method_identifier); + generator.emit(next_method_register); + + // 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(js_undefined()); + generator.emit(received_completion_value_register); + + // 7. Repeat, + auto& loop_block = generator.make_block(); + auto& continuation_block = generator.make_block(); + auto& loop_end_block = generator.make_block(); + + generator.emit(Bytecode::Label { loop_block }); + generator.switch_to_basic_block(loop_block); + + // a. If received.[[Type]] is normal, then + 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( + 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::Call::CallType::Call, next_method_register, iterator_register); + + // FIXME: ii. If generatorKind is async, set innerResult to ? Await(innerResult). + + // 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); + + // iv. Let done be ? IteratorComplete(innerResult). + generator.emit(); + + // 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( + 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(); + generator.emit(Bytecode::Label { loop_end_block }); + + generator.switch_to_basic_block(type_is_normal_not_done_block); + + // FIXME: vi. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerResult))). + // vii. Else, set received to Completion(GeneratorYield(innerResult)). + // FIXME: Else, + 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. + generator.emit(); + + generator.emit(Bytecode::Label { continuation_block }); + + // 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( + 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); + + // 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.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::Call::CallType::Call, throw_method_register, iterator_register); + + // FIXME: 2. If generatorKind is async, set innerResult to ? Await(innerResult). + + // 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); + + // 5. Let done be ? IteratorComplete(innerResult). + generator.emit(); + + // 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( + 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(); + generator.emit(Bytecode::Label { loop_end_block }); + + generator.switch_to_basic_block(type_is_throw_not_done_block); + + // FIXME: 7. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerResult))). + // 8. Else, set received to Completion(GeneratorYield(innerResult)). + // FIXME: Else, + 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. + generator.emit(); + + generator.emit(Bytecode::Label { continuation_block }); + + generator.switch_to_basic_block(throw_method_is_undefined_block); + + // 1. NOTE: If iterator does not have a throw method, this throw is going to terminate the yield* loop. But first we need to give iterator a chance to clean up. + + // 2. Let closeCompletion be Completion Record { [[Type]]: normal, [[Value]]: empty, [[Target]]: empty }. + // FIXME: 3. If generatorKind is async, perform ? AsyncIteratorClose(iteratorRecord, closeCompletion). + // 4. Else, perform ? IteratorClose(iteratorRecord, closeCompletion). + // FIXME: Else, + generator.emit(iterator_record_register); + generator.emit(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())); + generator.perform_needed_unwinds(); + generator.emit(); + + // 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); + + // 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( + Bytecode::Label { return_is_undefined_block }, + Bytecode::Label { return_is_defined_block }); + + generator.switch_to_basic_block(return_is_undefined_block); + + // FIXME: 1. If generatorKind is async, set received.[[Value]] to ? Await(received.[[Value]]). + // 2. Return ? received. + // NOTE: This will always be a return completion. + generator.emit(received_completion_value_register); + generator.perform_needed_unwinds(); + generator.emit(nullptr); + + 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::Call::CallType::Call, return_method_register, iterator_register); + + // FIXME: v. If generatorKind is async, set innerReturnResult to ? Await(innerReturnResult). + + // 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); + + // vii. Let done be ? IteratorComplete(innerReturnResult). + generator.emit(); + + // 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( + 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(); + + // 2. Return Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. + generator.perform_needed_unwinds(); + generator.emit(nullptr); + + generator.switch_to_basic_block(type_is_return_not_done_block); + + // FIXME: ix. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerReturnResult))). + // x. Else, set received to Completion(GeneratorYield(innerReturnResult)). + // FIXME: Else, + 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. + generator.emit(); + + generator.emit(Bytecode::Label { continuation_block }); + + generator.switch_to_basic_block(continuation_block); + get_received_completion_type_and_value(); + generator.emit(Bytecode::Label { loop_block }); + + generator.switch_to_basic_block(loop_end_block); + return {}; } if (m_argument) diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h index 524ea8dc3c..72255f2412 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.h +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h @@ -34,6 +34,7 @@ O(GetById) \ O(GetByValue) \ O(GetIterator) \ + O(GetMethod) \ O(GetNewTarget) \ O(GetObjectPropertyIterator) \ O(GetVariable) \ @@ -42,6 +43,7 @@ O(In) \ O(Increment) \ O(InstanceOf) \ + O(IteratorClose) \ O(IteratorNext) \ O(IteratorResultDone) \ O(IteratorResultValue) \ @@ -68,6 +70,7 @@ O(NewObject) \ O(NewRegExp) \ O(NewString) \ + O(NewTypeError) \ O(Not) \ O(PushDeclarativeEnvironment) \ O(PutById) \ @@ -82,6 +85,7 @@ O(Sub) \ O(SuperCall) \ O(Throw) \ + O(ThrowIfNotObject) \ O(Typeof) \ O(TypeofVariable) \ O(UnaryMinus) \ diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index 920152cde1..597053b145 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -320,6 +320,21 @@ ThrowCompletionOr NewRegExp::execute_impl(Bytecode::Interpreter& interpret 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 {}; \ + } \ + DeprecatedString New##ErrorName::to_deprecated_string_impl(Bytecode::Executable const& executable) const \ + { \ + return DeprecatedString::formatted("New" #ErrorName " {} (\"{}\")", m_error_string, executable.string_table->get(m_error_string)); \ + } + +JS_ENUMERATE_NEW_BUILTIN_ERROR_OPS(JS_DEFINE_NEW_BUILTIN_ERROR_OP) + ThrowCompletionOr CopyObjectExcludingProperties::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); @@ -728,6 +743,14 @@ ThrowCompletionOr Throw::execute_impl(Bytecode::Interpreter& interpreter) return throw_completion(interpreter.accumulator()); } +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()); + return {}; +} + ThrowCompletionOr EnterUnwindContext::execute_impl(Bytecode::Interpreter& interpreter) const { interpreter.enter_unwind_context(m_handler_target, m_finalizer_target); @@ -857,6 +880,15 @@ ThrowCompletionOr GetIterator::execute_impl(Bytecode::Interpreter& interpr return {}; } +ThrowCompletionOr GetMethod::execute_impl(Bytecode::Interpreter& interpreter) const +{ + 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(); + return {}; +} + // 14.7.5.9 EnumerateObjectProperties ( O ), https://tc39.es/ecma262/#sec-enumerate-object-properties ThrowCompletionOr GetObjectPropertyIterator::execute_impl(Bytecode::Interpreter& interpreter) const { @@ -938,6 +970,17 @@ ThrowCompletionOr GetObjectPropertyIterator::execute_impl(Bytecode::Interp return {}; } +ThrowCompletionOr IteratorClose::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto* iterator_object = TRY(interpreter.accumulator().to_object(vm)); + auto iterator = object_to_iterator(vm, *iterator_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, {} })); + return {}; +} + ThrowCompletionOr IteratorNext::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); @@ -1210,6 +1253,11 @@ DeprecatedString Throw::to_deprecated_string_impl(Bytecode::Executable const&) c return "Throw"; } +DeprecatedString ThrowIfNotObject::to_deprecated_string_impl(Bytecode::Executable const&) const +{ + return "ThrowIfNotObject"; +} + DeprecatedString EnterUnwindContext::to_deprecated_string_impl(Bytecode::Executable const&) const { auto handler_string = m_handler_target.has_value() ? DeprecatedString::formatted("{}", *m_handler_target) : ""; @@ -1283,11 +1331,21 @@ DeprecatedString GetIterator::to_deprecated_string_impl(Executable const&) const return "GetIterator"; } +DeprecatedString GetMethod::to_deprecated_string_impl(Bytecode::Executable const& executable) const +{ + return DeprecatedString::formatted("GetMethod {} ({})", m_property, executable.identifier_table->get(m_property)); +} + DeprecatedString GetObjectPropertyIterator::to_deprecated_string_impl(Bytecode::Executable const&) const { return "GetObjectPropertyIterator"; } +DeprecatedString IteratorClose::to_deprecated_string_impl(Bytecode::Executable const&) const +{ + return DeprecatedString::formatted("IteratorClose completion_type={} completion_value={}", to_underlying(m_completion_type), m_completion_value.has_value() ? m_completion_value.value().to_string_without_side_effects() : ""); +} + DeprecatedString IteratorNext::to_deprecated_string_impl(Executable const&) const { return "IteratorNext"; diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index c0de8fb4e7..053406b51e 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -208,6 +208,34 @@ private: StringTableIndex m_flags_index; }; +#define JS_ENUMERATE_NEW_BUILTIN_ERROR_OPS(O) \ + O(TypeError) + +#define JS_DECLARE_NEW_BUILTIN_ERROR_OP(ErrorName) \ + class New##ErrorName final : public Instruction { \ + public: \ + explicit New##ErrorName(StringTableIndex error_string) \ + : Instruction(Type::New##ErrorName) \ + , m_error_string(error_string) \ + { \ + } \ + \ + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; \ + DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const; \ + void replace_references_impl(BasicBlock const&, BasicBlock const&) \ + { \ + } \ + void replace_references_impl(Register, Register) \ + { \ + } \ + \ + private: \ + StringTableIndex m_error_string; \ + }; + +JS_ENUMERATE_NEW_BUILTIN_ERROR_OPS(JS_DECLARE_NEW_BUILTIN_ERROR_OP) +#undef 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: @@ -819,6 +847,19 @@ public: void replace_references_impl(Register, Register) { } }; +class ThrowIfNotObject final : public Instruction { +public: + ThrowIfNotObject() + : Instruction(Type::ThrowIfNotObject) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const; + void replace_references_impl(BasicBlock const&, BasicBlock const&) { } + void replace_references_impl(Register, Register) { } +}; + class EnterUnwindContext final : public Instruction { public: constexpr static bool IsTerminator = true; @@ -953,6 +994,23 @@ public: void replace_references_impl(Register, Register) { } }; +class GetMethod final : public Instruction { +public: + GetMethod(IdentifierTableIndex property) + : Instruction(Type::GetMethod) + , m_property(property) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const; + void replace_references_impl(BasicBlock const&, BasicBlock const&) { } + void replace_references_impl(Register, Register) { } + +private: + IdentifierTableIndex m_property; +}; + class GetObjectPropertyIterator final : public Instruction { public: GetObjectPropertyIterator() @@ -966,6 +1024,25 @@ public: void replace_references_impl(Register, Register) { } }; +class IteratorClose final : public Instruction { +public: + IteratorClose(Completion::Type completion_type, Optional completion_value) + : Instruction(Type::IteratorClose) + , m_completion_type(completion_type) + , m_completion_value(completion_value) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const; + void replace_references_impl(BasicBlock const&, BasicBlock const&) { } + void replace_references_impl(Register, Register) { } + +private: + Completion::Type m_completion_type { Completion::Type::Normal }; + Optional m_completion_value; +}; + class IteratorNext final : public Instruction { public: IteratorNext() diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index a34395d0c6..bd4b1919c2 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -309,6 +309,7 @@ M(UnsupportedDeleteSuperProperty, "Can't delete a property on 'super'") \ M(WrappedFunctionCallThrowCompletion, "Call of wrapped target function did not complete normally") \ M(WrappedFunctionCopyNameAndLengthThrowCompletion, "Trying to copy target name and length did not complete normally") \ + M(YieldFromIteratorMissingThrowMethod, "yield* protocol violation: iterator must have a throw method") \ M(URIMalformed, "URI malformed") /* LibWeb bindings */ \ M(NotAByteString, "Argument to {}() must be a byte string") \ M(BadArgCountOne, "{}() needs one argument") \