diff options
author | Ali Mohammad Pur <ali.mpfard@gmail.com> | 2021-06-11 01:38:30 +0430 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-06-11 00:30:09 +0200 |
commit | 3234697ecacb41e15e230a450f2759f404c8b047 (patch) | |
tree | 54b9b4de011c95644a4ceb3ce694b79bdcf0ce49 | |
parent | c53a86a3fe530d6adaa095944a59a8a0333be3c6 (diff) | |
download | serenity-3234697ecacb41e15e230a450f2759f404c8b047.zip |
LibJS: Implement generator functions (only in bytecode mode)
21 files changed, 407 insertions, 34 deletions
diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 203bc880eb..f56a5c33bb 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -259,6 +259,12 @@ Value CallExpression::execute(Interpreter& interpreter, GlobalObject& global_obj return result; } +Value YieldExpression::execute(Interpreter&, GlobalObject&) const +{ + // This should be transformed to a return. + VERIFY_NOT_REACHED(); +} + Value ReturnStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const { InterpreterNodeScope node_scope { interpreter, *this }; @@ -1151,7 +1157,7 @@ void BindingPattern::dump(int indent) const void FunctionNode::dump(int indent, const String& class_name) const { print_indent(indent); - outln("{} '{}'", class_name, name()); + outln("{}{} '{}'", class_name, m_is_generator ? "*" : "", name()); if (!m_parameters.is_empty()) { print_indent(indent + 1); outln("(Parameters)"); @@ -1193,6 +1199,13 @@ void FunctionExpression::dump(int indent) const FunctionNode::dump(indent, class_name()); } +void YieldExpression::dump(int indent) const +{ + ASTNode::dump(indent); + if (argument()) + argument()->dump(indent + 1); +} + void ReturnStatement::dump(int indent) const { ASTNode::dump(indent); diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index 946a60feaa..1f32544e71 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -227,14 +227,16 @@ public: const Vector<Parameter>& parameters() const { return m_parameters; }; i32 function_length() const { return m_function_length; } bool is_strict_mode() const { return m_is_strict_mode; } + bool is_generator() const { return m_is_generator; } protected: - FunctionNode(const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, i32 function_length, NonnullRefPtrVector<VariableDeclaration> variables, bool is_strict_mode) + FunctionNode(const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, i32 function_length, NonnullRefPtrVector<VariableDeclaration> variables, bool is_generator, bool is_strict_mode) : m_name(name) , m_body(move(body)) , m_parameters(move(parameters)) , m_variables(move(variables)) , m_function_length(function_length) + , m_is_generator(is_generator) , m_is_strict_mode(is_strict_mode) { } @@ -256,6 +258,7 @@ private: const Vector<Parameter> m_parameters; NonnullRefPtrVector<VariableDeclaration> m_variables; const i32 m_function_length; + bool m_is_generator; bool m_is_strict_mode; }; @@ -265,9 +268,9 @@ class FunctionDeclaration final public: static bool must_have_name() { return true; } - FunctionDeclaration(SourceRange source_range, const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, i32 function_length, NonnullRefPtrVector<VariableDeclaration> variables, bool is_strict_mode = false) + FunctionDeclaration(SourceRange source_range, const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, i32 function_length, NonnullRefPtrVector<VariableDeclaration> variables, bool is_generator, bool is_strict_mode = false) : Declaration(move(source_range)) - , FunctionNode(name, move(body), move(parameters), function_length, move(variables), is_strict_mode) + , FunctionNode(name, move(body), move(parameters), function_length, move(variables), is_generator, is_strict_mode) { } @@ -282,9 +285,9 @@ class FunctionExpression final public: static bool must_have_name() { return false; } - FunctionExpression(SourceRange source_range, const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, i32 function_length, NonnullRefPtrVector<VariableDeclaration> variables, bool is_strict_mode, bool is_arrow_function = false) + FunctionExpression(SourceRange source_range, const FlyString& name, NonnullRefPtr<Statement> body, Vector<Parameter> parameters, i32 function_length, NonnullRefPtrVector<VariableDeclaration> variables, bool is_generator, bool is_strict_mode, bool is_arrow_function = false) : Expression(source_range) - , FunctionNode(name, move(body), move(parameters), function_length, move(variables), is_strict_mode) + , FunctionNode(name, move(body), move(parameters), function_length, move(variables), is_generator, is_strict_mode) , m_is_arrow_function(is_arrow_function) { } @@ -318,6 +321,24 @@ public: Value execute(Interpreter&, GlobalObject&) const override { return {}; } }; +class YieldExpression final : public Expression { +public: + explicit YieldExpression(SourceRange source_range, RefPtr<Expression> argument) + : Expression(move(source_range)) + , m_argument(move(argument)) + { + } + + const Expression* argument() const { return m_argument; } + + virtual Value execute(Interpreter&, GlobalObject&) const override; + virtual void dump(int indent) const override; + virtual void generate_bytecode(Bytecode::Generator&) const override; + +private: + RefPtr<Expression> m_argument; +}; + class ReturnStatement final : public Statement { public: explicit ReturnStatement(SourceRange source_range, RefPtr<Expression> argument) diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 6397ca2e8c..e5e3704bbe 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -668,7 +668,23 @@ void ReturnStatement::generate_bytecode(Bytecode::Generator& generator) const { if (m_argument) m_argument->generate_bytecode(generator); - generator.emit<Bytecode::Op::Return>(); + + if (generator.is_in_generator_function()) + generator.emit<Bytecode::Op::Yield>(nullptr); + else + generator.emit<Bytecode::Op::Return>(); +} + +void YieldExpression::generate_bytecode(Bytecode::Generator& generator) const +{ + VERIFY(generator.is_in_generator_function()); + + if (m_argument) + m_argument->generate_bytecode(generator); + + auto& continuation_block = generator.make_block(); + generator.emit<Bytecode::Op::Yield>(Bytecode::Label { continuation_block }); + generator.switch_to_basic_block(continuation_block); } void IfStatement::generate_bytecode(Bytecode::Generator& generator) const diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.cpp b/Userland/Libraries/LibJS/Bytecode/Generator.cpp index c7e394ef2f..7d356588f3 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Generator.cpp @@ -23,11 +23,28 @@ Generator::~Generator() { } -Executable Generator::generate(ASTNode const& node) +Executable Generator::generate(ASTNode const& node, bool is_in_generator_function) { Generator generator; generator.switch_to_basic_block(generator.make_block()); + if (is_in_generator_function) { + generator.enter_generator_context(); + // Immediately yield with no value. + auto& start_block = generator.make_block(); + generator.emit<Bytecode::Op::Yield>(Label { start_block }); + generator.switch_to_basic_block(start_block); + } node.generate_bytecode(generator); + if (is_in_generator_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<Bytecode::Op::LoadImmediate>(js_undefined()); + generator.emit<Bytecode::Op::Yield>(nullptr); + } + } return { move(generator.m_root_basic_blocks), move(generator.m_string_table), generator.m_next_register }; } diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.h b/Userland/Libraries/LibJS/Bytecode/Generator.h index 1ae3e0ab94..49b34e138a 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.h +++ b/Userland/Libraries/LibJS/Bytecode/Generator.h @@ -28,7 +28,7 @@ struct Executable { class Generator { public: - static Executable generate(ASTNode const&); + static Executable generate(ASTNode const&, bool is_in_generator_function = false); Register allocate_register(); @@ -109,6 +109,10 @@ public: return m_string_table->insert(string); } + bool is_in_generator_function() const { return m_is_in_generator_function; } + void enter_generator_context() { m_is_in_generator_function = true; } + void leave_generator_context() { m_is_in_generator_function = false; } + private: Generator(); ~Generator(); @@ -122,6 +126,7 @@ private: u32 m_next_register { 2 }; u32 m_next_block { 1 }; + bool m_is_in_generator_function { false }; Vector<Label> m_continuable_scopes; Vector<Label> m_breakable_scopes; }; diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h index 901b291139..b3a622898f 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.h +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h @@ -61,7 +61,8 @@ O(PushLexicalEnvironment) \ O(EnterUnwindContext) \ O(LeaveUnwindContext) \ - O(ContinuePendingUnwind) + O(ContinuePendingUnwind) \ + O(Yield) namespace JS::Bytecode { diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp index fb2a493d83..94c3e1e512 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -35,7 +35,7 @@ Interpreter::~Interpreter() s_current = nullptr; } -Value Interpreter::run(Executable const& executable) +Value Interpreter::run(Executable const& executable, BasicBlock const* entry_point) { dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter will run unit {:p}", &executable); @@ -56,10 +56,14 @@ Value Interpreter::run(Executable const& executable) VERIFY(!vm().exception()); } - auto block = &executable.basic_blocks.first(); - m_register_windows.append(make<RegisterWindow>()); - registers().resize(executable.number_of_registers); - registers()[Register::global_object_index] = Value(&global_object()); + auto block = entry_point ?: &executable.basic_blocks.first(); + if (m_manually_entered_frames) { + VERIFY(registers().size() >= executable.number_of_registers); + } else { + m_register_windows.append(make<RegisterWindow>()); + registers().resize(executable.number_of_registers); + registers()[Register::global_object_index] = Value(&global_object()); + } for (;;) { Bytecode::InstructionStreamIterator pc(block->instruction_stream()); @@ -124,7 +128,8 @@ Value Interpreter::run(Executable const& executable) vm().set_last_value(Badge<Interpreter> {}, accumulator()); - m_register_windows.take_last(); + if (!m_manually_entered_frames) + m_register_windows.take_last(); auto return_value = m_return_value.value_or(js_undefined()); m_return_value = {}; diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.h b/Userland/Libraries/LibJS/Bytecode/Interpreter.h index 2e2c3f8109..26b2dc9078 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.h +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.h @@ -30,10 +30,23 @@ public: GlobalObject& global_object() { return m_global_object; } VM& vm() { return m_vm; } - Value run(Bytecode::Executable const&); + Value run(Bytecode::Executable const&, Bytecode::BasicBlock const* entry_point = nullptr); ALWAYS_INLINE Value& accumulator() { return reg(Register::accumulator()); } Value& reg(Register const& r) { return registers()[r.index()]; } + [[nodiscard]] RegisterWindow snapshot_frame() const { return m_register_windows.last(); } + + void enter_frame(RegisterWindow const& frame) + { + ++m_manually_entered_frames; + m_register_windows.append(make<RegisterWindow>(frame)); + } + void leave_frame() + { + VERIFY(m_manually_entered_frames); + --m_manually_entered_frames; + m_register_windows.take_last(); + } void jump(Label const& label) { @@ -55,6 +68,7 @@ private: NonnullOwnPtrVector<RegisterWindow> m_register_windows; Optional<BasicBlock const*> m_pending_jump; Value m_return_value; + size_t m_manually_entered_frames { 0 }; Executable const* m_current_executable { nullptr }; Vector<UnwindInfo> m_unwind_contexts; Handle<Exception> m_saved_exception; diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index f97fd1b538..2d080627c1 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -218,8 +218,7 @@ void Call::execute(Bytecode::Interpreter& interpreter) const void NewFunction::execute(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); - auto& global_object = interpreter.global_object(); - interpreter.accumulator() = ScriptFunction::create(global_object, m_function_node.name(), m_function_node.body(), m_function_node.parameters(), m_function_node.function_length(), vm.current_scope(), m_function_node.is_strict_mode()); + interpreter.accumulator() = ScriptFunction::create(interpreter.global_object(), m_function_node.name(), m_function_node.body(), m_function_node.parameters(), m_function_node.function_length(), vm.current_scope(), m_function_node.is_generator(), m_function_node.is_strict_mode()); } void Return::execute(Bytecode::Interpreter& interpreter) const @@ -280,6 +279,18 @@ void PushLexicalEnvironment::execute(Bytecode::Interpreter& interpreter) const interpreter.vm().call_frame().scope = block_lexical_environment; } +void Yield::execute(Bytecode::Interpreter& interpreter) const +{ + auto yielded_value = interpreter.accumulator().value_or(js_undefined()); + auto object = JS::Object::create_empty(interpreter.global_object()); + object->put("result", yielded_value); + if (m_continuation_label.has_value()) + object->put("continuation", Value(static_cast<double>(reinterpret_cast<u64>(&m_continuation_label->block())))); + else + object->put("continuation", Value(0)); + interpreter.do_return(object); +} + String Load::to_string(Bytecode::Executable const&) const { return String::formatted("Load {}", m_src); @@ -445,4 +456,11 @@ String PushLexicalEnvironment::to_string(const Bytecode::Executable& executable) return builder.to_string(); } +String Yield::to_string(Bytecode::Executable const&) const +{ + if (m_continuation_label.has_value()) + return String::formatted("Yield continuation:@{}", m_continuation_label->block().name()); + return String::formatted("Yield return"); +} + } diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index ca0497807e..f5961b87e0 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -184,8 +184,6 @@ public: void execute(Bytecode::Interpreter&) const; String to_string(Bytecode::Executable const&) const; - size_t length() const { return sizeof(*this) + sizeof(Register) * m_element_count; } - private: size_t m_element_count { 0 }; Register m_elements[]; @@ -462,6 +460,28 @@ private: Label m_resume_target; }; +class Yield final : public Instruction { +public: + constexpr static bool IsTerminator = true; + + explicit Yield(Label continuation_label) + : Instruction(Type::Yield) + , m_continuation_label(continuation_label) + { + } + + explicit Yield(std::nullptr_t) + : Instruction(Type::Yield) + { + } + + void execute(Bytecode::Interpreter&) const; + String to_string(Bytecode::Executable const&) const; + +private: + Optional<Label> m_continuation_label; +}; + class PushLexicalEnvironment final : public Instruction { public: PushLexicalEnvironment(HashMap<u32, Variable> variables) @@ -472,6 +492,7 @@ public: void execute(Bytecode::Interpreter&) const; String to_string(Bytecode::Executable const&) const; +private: HashMap<u32, Variable> m_variables; }; } diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index ffcba7b6a1..104538c481 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -45,6 +45,7 @@ set(SOURCES Runtime/FunctionConstructor.cpp Runtime/Function.cpp Runtime/FunctionPrototype.cpp + Runtime/GeneratorObject.cpp Runtime/GlobalObject.cpp Runtime/IndexedProperties.cpp Runtime/IteratorOperations.cpp diff --git a/Userland/Libraries/LibJS/Interpreter.cpp b/Userland/Libraries/LibJS/Interpreter.cpp index db06157740..2b383987a3 100644 --- a/Userland/Libraries/LibJS/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Interpreter.cpp @@ -79,7 +79,7 @@ void Interpreter::enter_scope(const ScopeNode& scope_node, ScopeType scope_type, { ScopeGuard guard([&] { for (auto& declaration : scope_node.functions()) { - auto* function = ScriptFunction::create(global_object, declaration.name(), declaration.body(), declaration.parameters(), declaration.function_length(), current_scope(), declaration.is_strict_mode()); + auto* function = ScriptFunction::create(global_object, declaration.name(), declaration.body(), declaration.parameters(), declaration.function_length(), current_scope(), declaration.is_generator(), declaration.is_strict_mode()); vm().set_variable(declaration.name(), function, global_object); } }); diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index c1a8051f94..bca6539426 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -590,9 +590,9 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_ constructor_body->append(create_ast_node<ExpressionStatement>({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, move(super_call))); constructor_body->add_variables(m_parser_state.m_var_scopes.last()); - constructor = create_ast_node<FunctionExpression>({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, class_name, move(constructor_body), Vector { FunctionNode::Parameter { FlyString { "args" }, nullptr, true } }, 0, NonnullRefPtrVector<VariableDeclaration>(), true); + constructor = create_ast_node<FunctionExpression>({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, class_name, move(constructor_body), Vector { FunctionNode::Parameter { FlyString { "args" }, nullptr, true } }, 0, NonnullRefPtrVector<VariableDeclaration>(), false, true); } else { - constructor = create_ast_node<FunctionExpression>({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, class_name, move(constructor_body), Vector<FunctionNode::Parameter> {}, 0, NonnullRefPtrVector<VariableDeclaration>(), true); + constructor = create_ast_node<FunctionExpression>({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, class_name, move(constructor_body), Vector<FunctionNode::Parameter> {}, 0, NonnullRefPtrVector<VariableDeclaration>(), false, true); } } @@ -634,6 +634,7 @@ NonnullRefPtr<Expression> Parser::parse_primary_expression() syntax_error("'super' keyword unexpected here"); return create_ast_node<SuperExpression>({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }); case TokenType::Identifier: { + read_as_identifier:; if (!try_parse_arrow_function_expression_failed_at_position(position())) { auto arrow_function_result = try_parse_arrow_function_expression(false); if (!arrow_function_result.is_null()) @@ -674,6 +675,10 @@ NonnullRefPtr<Expression> Parser::parse_primary_expression() } return parse_new_expression(); } + case TokenType::Yield: + if (!m_parser_state.m_in_generator_function_context) + goto read_as_identifier; + return parse_yield_expression(); default: expected("primary expression"); consume(); @@ -1256,6 +1261,16 @@ NonnullRefPtr<NewExpression> Parser::parse_new_expression() return create_ast_node<NewExpression>({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, move(callee), move(arguments)); } +NonnullRefPtr<YieldExpression> Parser::parse_yield_expression() +{ + auto rule_start = push_start(); + consume(TokenType::Yield); + RefPtr<Expression> argument; + if (match_expression()) + argument = parse_expression(0); + return create_ast_node<YieldExpression>({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, move(argument)); +} + NonnullRefPtr<ReturnStatement> Parser::parse_return_statement() { auto rule_start = push_start(); @@ -1336,9 +1351,14 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u8 parse_options) ScopePusher scope(*this, ScopePusher::Var | ScopePusher::Function); + auto is_generator = false; String name; if (parse_options & FunctionNodeParseOptions::CheckForFunctionAndName) { consume(TokenType::Function); + is_generator = match(TokenType::Asterisk); + if (is_generator) + consume(TokenType::Asterisk); + if (FunctionNodeType::must_have_name() || match(TokenType::Identifier)) name = consume(TokenType::Identifier).value(); } @@ -1351,6 +1371,7 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u8 parse_options) function_length = parameters.size(); TemporaryChange change(m_parser_state.m_in_function_context, true); + TemporaryChange generator_change(m_parser_state.m_in_generator_function_context, m_parser_state.m_in_generator_function_context || is_generator); auto old_labels_in_scope = move(m_parser_state.m_labels_in_scope); ScopeGuard guard([&]() { m_parser_state.m_labels_in_scope = move(old_labels_in_scope); @@ -1360,7 +1381,7 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u8 parse_options) auto body = parse_block_statement(is_strict); body->add_variables(m_parser_state.m_var_scopes.last()); body->add_functions(m_parser_state.m_function_scopes.last()); - return create_ast_node<FunctionNodeType>({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, name, move(body), move(parameters), function_length, NonnullRefPtrVector<VariableDeclaration>(), is_strict); + return create_ast_node<FunctionNodeType>({ m_parser_state.m_current_token.filename(), rule_start.position(), position() }, name, move(body), move(parameters), function_length, NonnullRefPtrVector<VariableDeclaration>(), is_generator, is_strict); } Vector<FunctionNode::Parameter> Parser::parse_formal_parameters(int& function_length, u8 parse_options) @@ -2009,6 +2030,7 @@ bool Parser::match_expression() const || type == TokenType::This || type == TokenType::Super || type == TokenType::RegexLiteral + || type == TokenType::Yield || match_unary_prefixed_expression(); } @@ -2085,6 +2107,7 @@ bool Parser::match_statement() const auto type = m_parser_state.m_current_token.type(); return match_expression() || type == TokenType::Return + || type == TokenType::Yield || type == TokenType::Do || type == TokenType::If || type == TokenType::Throw diff --git a/Userland/Libraries/LibJS/Parser.h b/Userland/Libraries/LibJS/Parser.h index 85000c7a1e..83a6a01e5e 100644 --- a/Userland/Libraries/LibJS/Parser.h +++ b/Userland/Libraries/LibJS/Parser.h @@ -77,6 +77,7 @@ public: NonnullRefPtr<NewExpression> parse_new_expression(); NonnullRefPtr<ClassDeclaration> parse_class_declaration(); NonnullRefPtr<ClassExpression> parse_class_expression(bool expect_class_name); + NonnullRefPtr<YieldExpression> parse_yield_expression(); NonnullRefPtr<Expression> parse_property_key(); NonnullRefPtr<AssignmentExpression> parse_assignment_expression(AssignmentOp, NonnullRefPtr<Expression> lhs, int min_precedence, Associativity); @@ -200,6 +201,7 @@ private: bool m_allow_super_property_lookup { false }; bool m_allow_super_constructor_call { false }; bool m_in_function_context { false }; + bool m_in_generator_function_context { false }; bool m_in_arrow_function_context { false }; bool m_in_break_context { false }; bool m_in_continue_context { false }; diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 52dc0ef420..d0254ec4e9 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -293,6 +293,8 @@ struct CommonPropertyNames { FlyString catch_ { "catch" }; FlyString delete_ { "delete" }; FlyString for_ { "for" }; + FlyString return_ { "return" }; + FlyString throw_ { "throw" }; #define __ENUMERATE(x) FlyString x { #x }; ENUMERATE_STANDARD_PROPERTY_NAMES(__ENUMERATE) #undef __ENUMERATE diff --git a/Userland/Libraries/LibJS/Runtime/GeneratorObject.cpp b/Userland/Libraries/LibJS/Runtime/GeneratorObject.cpp new file mode 100644 index 0000000000..9422ed168c --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/GeneratorObject.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/TemporaryChange.h> +#include <LibJS/Bytecode/Generator.h> +#include <LibJS/Bytecode/Interpreter.h> +#include <LibJS/Runtime/GeneratorObject.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS { + +GeneratorObject* GeneratorObject::create(GlobalObject& global_object, Value initial_value, ScriptFunction* generating_function, ScopeObject* generating_scope, Bytecode::RegisterWindow frame) +{ + auto object = global_object.heap().allocate<GeneratorObject>(global_object, global_object); + object->m_generating_function = generating_function; + object->m_scope = generating_scope; + object->m_frame = move(frame); + object->m_previous_value = initial_value; + return object; +} + +GeneratorObject::GeneratorObject(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void GeneratorObject::initialize(GlobalObject& global_object) +{ + auto& vm = this->vm(); + Object::initialize(global_object); + define_native_function(vm.names.next, next); + define_native_function(vm.names.return_, return_); + define_native_function(vm.names.throw_, throw_); +} + +GeneratorObject::~GeneratorObject() +{ +} + +void GeneratorObject::visit_edges(Cell::Visitor& visitor) +{ + Object::visit_edges(visitor); + visitor.visit(m_scope); + visitor.visit(m_generating_function); + if (m_previous_value.is_object()) + visitor.visit(&m_previous_value.as_object()); +} + +GeneratorObject* GeneratorObject::typed_this(VM& vm, GlobalObject& global_object) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + if (!is<GeneratorObject>(this_object)) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Generator"); + return nullptr; + } + return static_cast<GeneratorObject*>(this_object); +} + +Value GeneratorObject::next_impl(VM& vm, GlobalObject& global_object, Optional<Value> value_to_throw) +{ + auto bytecode_interpreter = Bytecode::Interpreter::current(); + VERIFY(bytecode_interpreter); + + auto generated_value = [](Value value) { + if (value.is_object()) + return value.as_object().get("result"); + return value.is_empty() ? js_undefined() : value; + }; + + auto generated_continuation = [&](Value value) -> Bytecode::BasicBlock const* { + if (value.is_object()) + return reinterpret_cast<Bytecode::BasicBlock const*>(static_cast<u64>(value.as_object().get("continuation").to_double(global_object))); + return nullptr; + }; + + Value previous_generated_value { generated_value(m_previous_value) }; + + if (vm.exception()) + return {}; + + auto result = Object::create_empty(global_object); + result->put("value", previous_generated_value); + + if (m_done) { + result->put("done", Value(true)); + return result; + } + + // Extract the continuation + auto next_block = generated_continuation(m_previous_value); + if (vm.exception()) + return {}; + + if (!next_block) { + // The generator has terminated, now we can simply return done=true. + m_done = true; + result->put("done", Value(true)); + return result; + } + + // Make sure it's an actual block + VERIFY(!m_generating_function->bytecode_executable()->basic_blocks.find_if([next_block](auto& block) { return block == next_block; }).is_end()); + + // Restore the snapshot registers + bytecode_interpreter->enter_frame(m_frame); + + // Pretend that 'yield' returned the passed value, or threw + if (value_to_throw.has_value()) { + vm.throw_exception(global_object, value_to_throw.release_value()); + bytecode_interpreter->accumulator() = js_undefined(); + } else { + bytecode_interpreter->accumulator() = vm.argument(0); + } + + // Temporarily switch to the captured scope + TemporaryChange change { vm.call_frame().scope, m_scope }; + + m_previous_value = bytecode_interpreter->run(*m_generating_function->bytecode_executable(), next_block); + + bytecode_interpreter->leave_frame(); + + m_done = generated_continuation(m_previous_value) == nullptr; + + result->put("value", generated_value(m_previous_value)); + result->put("done", Value(m_done)); + + if (vm.exception()) + return {}; + + return result; +} + +JS_DEFINE_NATIVE_FUNCTION(GeneratorObject::next) +{ + auto object = typed_this(vm, global_object); + if (!object) + return {}; + return object->next_impl(vm, global_object, {}); +} + +JS_DEFINE_NATIVE_FUNCTION(GeneratorObject::return_) +{ + auto object = typed_this(vm, global_object); + if (!object) + return {}; + object->m_done = true; + return object->next_impl(vm, global_object, {}); +} + +JS_DEFINE_NATIVE_FUNCTION(GeneratorObject::throw_) +{ + auto object = typed_this(vm, global_object); + if (!object) + return {}; + return object->next_impl(vm, global_object, vm.argument(0)); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/GeneratorObject.h b/Userland/Libraries/LibJS/Runtime/GeneratorObject.h new file mode 100644 index 0000000000..e25d8e8198 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/GeneratorObject.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibJS/Runtime/Object.h> +#include <LibJS/Runtime/ScriptFunction.h> + +namespace JS { + +class GeneratorObject final : public Object { + JS_OBJECT(GeneratorObject, Object); + +public: + static GeneratorObject* create(GlobalObject&, Value, ScriptFunction*, ScopeObject*, Bytecode::RegisterWindow); + explicit GeneratorObject(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~GeneratorObject() override; + void visit_edges(Cell::Visitor&) override; + + static GeneratorObject* typed_this(VM&, GlobalObject&); + +private: + JS_DECLARE_NATIVE_FUNCTION(next); + JS_DECLARE_NATIVE_FUNCTION(return_); + JS_DECLARE_NATIVE_FUNCTION(throw_); + + Value next_impl(VM&, GlobalObject&, Optional<Value> value_to_throw); + + ScopeObject* m_scope { nullptr }; + ScriptFunction* m_generating_function { nullptr }; + Value m_previous_value; + Bytecode::RegisterWindow m_frame; + bool m_done { false }; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp b/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp index f57dad44eb..d91f65bc3a 100644 --- a/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp +++ b/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp @@ -13,7 +13,9 @@ #include <LibJS/Interpreter.h> #include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GeneratorObject.h> #include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/NativeFunction.h> #include <LibJS/Runtime/ScriptFunction.h> #include <LibJS/Runtime/Value.h> @@ -31,18 +33,19 @@ static ScriptFunction* typed_this(VM& vm, GlobalObject& global_object) return static_cast<ScriptFunction*>(this_object); } -ScriptFunction* ScriptFunction::create(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, bool is_strict, bool is_arrow_function) +ScriptFunction* ScriptFunction::create(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, bool is_generator, bool is_strict, bool is_arrow_function) { - return global_object.heap().allocate<ScriptFunction>(global_object, global_object, name, body, move(parameters), m_function_length, parent_scope, *global_object.function_prototype(), is_strict, is_arrow_function); + return global_object.heap().allocate<ScriptFunction>(global_object, global_object, name, body, move(parameters), m_function_length, parent_scope, *global_object.function_prototype(), is_generator, is_strict, is_arrow_function); } -ScriptFunction::ScriptFunction(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, Object& prototype, bool is_strict, bool is_arrow_function) +ScriptFunction::ScriptFunction(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, Object& prototype, bool is_generator, bool is_strict, bool is_arrow_function) : Function(prototype, is_arrow_function ? vm().this_value(global_object) : Value(), {}) , m_name(name) , m_body(body) , m_parameters(move(parameters)) , m_parent_scope(parent_scope) , m_function_length(m_function_length) + , m_is_generator(is_generator) , m_is_strict(is_strict) , m_is_arrow_function(is_arrow_function) { @@ -152,15 +155,20 @@ Value ScriptFunction::execute_function_body() if (bytecode_interpreter) { prepare_arguments(); if (!m_bytecode_executable.has_value()) { - m_bytecode_executable = Bytecode::Generator::generate(m_body); + m_bytecode_executable = Bytecode::Generator::generate(m_body, m_is_generator); if constexpr (JS_BYTECODE_DEBUG) { dbgln("Compiled Bytecode::Block for function '{}':", m_name); for (auto& block : m_bytecode_executable->basic_blocks) block.dump(*m_bytecode_executable); } } - return bytecode_interpreter->run(*m_bytecode_executable); + auto result = bytecode_interpreter->run(*m_bytecode_executable); + if (!m_is_generator) + return result; + + return GeneratorObject::create(global_object(), result, this, vm.call_frame().scope, bytecode_interpreter->snapshot_frame()); } else { + VERIFY(!m_is_generator); OwnPtr<Interpreter> local_interpreter; ast_interpreter = vm.interpreter_if_exists(); diff --git a/Userland/Libraries/LibJS/Runtime/ScriptFunction.h b/Userland/Libraries/LibJS/Runtime/ScriptFunction.h index edc748130b..fa93085e5d 100644 --- a/Userland/Libraries/LibJS/Runtime/ScriptFunction.h +++ b/Userland/Libraries/LibJS/Runtime/ScriptFunction.h @@ -16,9 +16,9 @@ class ScriptFunction final : public Function { JS_OBJECT(ScriptFunction, Function); public: - static ScriptFunction* create(GlobalObject&, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, bool is_strict, bool is_arrow_function = false); + static ScriptFunction* create(GlobalObject&, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, bool is_generator, bool is_strict, bool is_arrow_function = false); - ScriptFunction(GlobalObject&, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, Object& prototype, bool is_strict, bool is_arrow_function = false); + ScriptFunction(GlobalObject&, const FlyString& name, const Statement& body, Vector<FunctionNode::Parameter> parameters, i32 m_function_length, ScopeObject* parent_scope, Object& prototype, bool is_generator, bool is_strict, bool is_arrow_function = false); virtual void initialize(GlobalObject&) override; virtual ~ScriptFunction(); @@ -33,6 +33,8 @@ public: void set_is_class_constructor() { m_is_class_constructor = true; }; + auto& bytecode_executable() const { return m_bytecode_executable; } + protected: virtual bool is_strict_mode() const final { return m_is_strict; } @@ -51,6 +53,7 @@ private: Optional<Bytecode::Executable> m_bytecode_executable; ScopeObject* m_parent_scope { nullptr }; i32 m_function_length { 0 }; + bool m_is_generator { false }; bool m_is_strict { false }; bool m_is_arrow_function { false }; bool m_is_class_constructor { false }; diff --git a/Userland/Libraries/LibWeb/HTML/GlobalEventHandlers.cpp b/Userland/Libraries/LibWeb/HTML/GlobalEventHandlers.cpp index f6f49a72d8..320090d4e2 100644 --- a/Userland/Libraries/LibWeb/HTML/GlobalEventHandlers.cpp +++ b/Userland/Libraries/LibWeb/HTML/GlobalEventHandlers.cpp @@ -49,7 +49,7 @@ void GlobalEventHandlers::set_event_handler_attribute(const FlyString& name, HTM dbgln("Failed to parse script in event handler attribute '{}'", name); return; } - auto* function = JS::ScriptFunction::create(self.script_execution_context()->interpreter().global_object(), name, program->body(), program->parameters(), program->function_length(), nullptr, false, false); + auto* function = JS::ScriptFunction::create(self.script_execution_context()->interpreter().global_object(), name, program->body(), program->parameters(), program->function_length(), nullptr, false, false, false); VERIFY(function); listener = adopt_ref(*new DOM::EventListener(JS::make_handle(static_cast<JS::Function*>(function)))); } diff --git a/Userland/Libraries/LibWeb/HTML/WebSocket.cpp b/Userland/Libraries/LibWeb/HTML/WebSocket.cpp index 6bc9fdf50f..3c5b524367 100644 --- a/Userland/Libraries/LibWeb/HTML/WebSocket.cpp +++ b/Userland/Libraries/LibWeb/HTML/WebSocket.cpp @@ -246,7 +246,7 @@ void WebSocket::set_event_handler_attribute(const FlyString& name, HTML::EventHa dbgln("Failed to parse script in event handler attribute '{}'", name); return; } - auto* function = JS::ScriptFunction::create(script_execution_context()->interpreter().global_object(), name, program->body(), program->parameters(), program->function_length(), nullptr, false, false); + auto* function = JS::ScriptFunction::create(script_execution_context()->interpreter().global_object(), name, program->body(), program->parameters(), program->function_length(), nullptr, false, false, false); VERIFY(function); listener = adopt_ref(*new DOM::EventListener(JS::make_handle(static_cast<JS::Function*>(function)))); } |