diff options
author | Andreas Kling <kling@serenityos.org> | 2021-06-05 15:53:36 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-06-07 18:11:59 +0200 |
commit | 80b1604b0a90b85f1fbdfecfcffcbf73e5b485bc (patch) | |
tree | 9f5a39f3b47842905c62bc3eb3096f04582937c2 /Userland/Libraries | |
parent | b609fc6d516f0215a2df92f17c6137afde513c72 (diff) | |
download | serenity-80b1604b0a90b85f1fbdfecfcffcbf73e5b485bc.zip |
LibJS: Compile ScriptFunctions into bytecode and run them that way :^)
If there's a current Bytecode::Interpreter in action, ScriptFunction
will now compile itself into bytecode and execute in that context.
This patch also adds the Return bytecode instruction so that we can
actually return values from called functions. :^)
Return values are propagated from callee to caller via the caller's
$0 register. Bytecode::Interpreter now keeps a stack of register
"windows". These are not very efficient, but it should be pretty
straightforward to convert them to e.g a sliding register window
architecture later on.
This is pretty dang cool! :^)
Diffstat (limited to 'Userland/Libraries')
-rw-r--r-- | Userland/Libraries/LibJS/AST.h | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp | 10 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Bytecode/Interpreter.cpp | 59 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Bytecode/Interpreter.h | 16 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Bytecode/Op.cpp | 13 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Bytecode/Op.h | 15 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp | 89 |
7 files changed, 152 insertions, 51 deletions
diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index d6ff389251..4e156ef655 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -329,6 +329,7 @@ public: virtual Value execute(Interpreter&, GlobalObject&) const override; virtual void dump(int indent) const override; + virtual Optional<Bytecode::Register> generate_bytecode(Bytecode::Generator&) const override; private: RefPtr<Expression> m_argument; diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 32d80310be..18d4e24700 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -177,4 +177,14 @@ Optional<Bytecode::Register> CallExpression::generate_bytecode(Bytecode::Generat return dst_reg; } +Optional<Bytecode::Register> ReturnStatement::generate_bytecode(Bytecode::Generator& generator) const +{ + Optional<Bytecode::Register> argument_reg; + if (m_argument) + argument_reg = m_argument->generate_bytecode(generator); + + generator.emit<Bytecode::Op::Return>(argument_reg); + return argument_reg; +} + } diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp index 535acc486f..121b2ca066 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -11,32 +11,46 @@ namespace JS::Bytecode { +static Interpreter* s_current; + +Interpreter* Interpreter::current() +{ + return s_current; +} + Interpreter::Interpreter(GlobalObject& global_object) : m_vm(global_object.vm()) , m_global_object(global_object) { + VERIFY(!s_current); + s_current = this; } Interpreter::~Interpreter() { + VERIFY(s_current == this); + s_current = nullptr; } -void Interpreter::run(Bytecode::Block const& block) +Value Interpreter::run(Bytecode::Block const& block) { dbgln("Bytecode::Interpreter will run block {:p}", &block); - CallFrame global_call_frame; - global_call_frame.this_value = &global_object(); - static FlyString global_execution_context_name = "(*BC* global execution context)"; - global_call_frame.function_name = global_execution_context_name; - global_call_frame.scope = &global_object(); - VERIFY(!vm().exception()); - // FIXME: How do we know if we're in strict mode? Maybe the Bytecode::Block should know this? - // global_call_frame.is_strict_mode = ???; - vm().push_call_frame(global_call_frame, global_object()); - VERIFY(!vm().exception()); + if (vm().call_stack().is_empty()) { + CallFrame global_call_frame; + global_call_frame.this_value = &global_object(); + static FlyString global_execution_context_name = "(*BC* global execution context)"; + global_call_frame.function_name = global_execution_context_name; + global_call_frame.scope = &global_object(); + VERIFY(!vm().exception()); + // FIXME: How do we know if we're in strict mode? Maybe the Bytecode::Block should know this? + // global_call_frame.is_strict_mode = ???; + vm().push_call_frame(global_call_frame, global_object()); + VERIFY(!vm().exception()); + } - m_registers.resize(block.register_count()); + m_register_windows.append(make<RegisterWindow>()); + registers().resize(block.register_count()); size_t pc = 0; while (pc < block.instructions().size()) { @@ -46,18 +60,33 @@ void Interpreter::run(Bytecode::Block const& block) pc = m_pending_jump.release_value(); continue; } + if (!m_return_value.is_empty()) + break; ++pc; } dbgln("Bytecode::Interpreter did run block {:p}", &block); - for (size_t i = 0; i < m_registers.size(); ++i) { + for (size_t i = 0; i < registers().size(); ++i) { String value_string; - if (m_registers[i].is_empty()) + if (registers()[i].is_empty()) value_string = "(empty)"; else - value_string = m_registers[i].to_string_without_side_effects(); + value_string = registers()[i].to_string_without_side_effects(); dbgln("[{:3}] {}", i, value_string); } + + m_register_windows.take_last(); + + m_return_value = m_return_value.value_or(js_undefined()); + + // NOTE: The return value from a called function is put into $0 in the caller context. + if (!m_register_windows.is_empty()) + m_register_windows.last()[0] = m_return_value; + + if (vm().call_stack().size() == 1) + vm().pop_call_frame(); + + return m_return_value; } } diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.h b/Userland/Libraries/LibJS/Bytecode/Interpreter.h index dcb74b7fc2..f48cce2231 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.h +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.h @@ -6,6 +6,7 @@ #pragma once +#include <AK/NonnullOwnPtrVector.h> #include <LibJS/Bytecode/Label.h> #include <LibJS/Bytecode/Register.h> #include <LibJS/Forward.h> @@ -14,25 +15,34 @@ namespace JS::Bytecode { +using RegisterWindow = Vector<Value>; + class Interpreter { public: explicit Interpreter(GlobalObject&); ~Interpreter(); + // FIXME: Remove this thing once we don't need it anymore! + static Interpreter* current(); + GlobalObject& global_object() { return m_global_object; } VM& vm() { return m_vm; } - void run(Bytecode::Block const&); + Value run(Bytecode::Block const&); - Value& reg(Register const& r) { return m_registers[r.index()]; } + Value& reg(Register const& r) { return registers()[r.index()]; } void jump(Label const& label) { m_pending_jump = label.address(); } + void do_return(Value return_value) { m_return_value = return_value; } private: + RegisterWindow& registers() { return m_register_windows.last(); } + VM& m_vm; GlobalObject& m_global_object; - Vector<Value> m_registers; + NonnullOwnPtrVector<RegisterWindow> m_register_windows; Optional<size_t> m_pending_jump; + Value m_return_value; }; } diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index d40584067e..869dd169ce 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -133,6 +133,12 @@ void EnterScope::execute(Bytecode::Interpreter& interpreter) const // FIXME: Whatever else JS::Interpreter::enter_scope() does. } +void Return::execute(Bytecode::Interpreter& interpreter) const +{ + auto return_value = m_argument.has_value() ? interpreter.reg(m_argument.value()) : js_undefined(); + interpreter.do_return(return_value); +} + String Load::to_string() const { return String::formatted("Load dst:{}, value:{}", m_dst, m_value.to_string_without_side_effects()); @@ -228,4 +234,11 @@ String EnterScope::to_string() const return "EnterScope"; } +String Return::to_string() const +{ + if (m_argument.has_value()) + return String::formatted("Return {}", m_argument.value()); + return "Return"; +} + } diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index 793abff75d..1d57fed10b 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -301,4 +301,19 @@ private: ScopeNode const& m_scope_node; }; +class Return final : public Instruction { +public: + explicit Return(Optional<Register> argument) + : m_argument(move(argument)) + { + } + + virtual ~Return() override { } + virtual void execute(Bytecode::Interpreter&) const override; + virtual String to_string() const override; + +private: + Optional<Register> m_argument; +}; + } diff --git a/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp b/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp index e80954b4a0..4e4cf9698b 100644 --- a/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp +++ b/Userland/Libraries/LibJS/Runtime/ScriptFunction.cpp @@ -6,6 +6,9 @@ #include <AK/Function.h> #include <LibJS/AST.h> +#include <LibJS/Bytecode/Block.h> +#include <LibJS/Bytecode/Generator.h> +#include <LibJS/Bytecode/Interpreter.h> #include <LibJS/Interpreter.h> #include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/Error.h> @@ -110,45 +113,65 @@ Value ScriptFunction::execute_function_body() { auto& vm = this->vm(); - OwnPtr<Interpreter> local_interpreter; - Interpreter* interpreter = vm.interpreter_if_exists(); + Interpreter* ast_interpreter = nullptr; + auto* bytecode_interpreter = Bytecode::Interpreter::current(); + + auto prepare_arguments = [&] { + auto& call_frame_args = vm.call_frame().arguments; + for (size_t i = 0; i < m_parameters.size(); ++i) { + auto& parameter = m_parameters[i]; + parameter.binding.visit( + [&](const auto& param) { + Value argument_value; + if (parameter.is_rest) { + auto* array = Array::create(global_object()); + for (size_t rest_index = i; rest_index < call_frame_args.size(); ++rest_index) + array->indexed_properties().append(call_frame_args[rest_index]); + argument_value = move(array); + } else if (i < call_frame_args.size() && !call_frame_args[i].is_undefined()) { + argument_value = call_frame_args[i]; + } else if (parameter.default_value) { + // FIXME: Support default arguments in the bytecode world! + if (!bytecode_interpreter) + argument_value = parameter.default_value->execute(*ast_interpreter, global_object()); + if (vm.exception()) + return; + } else { + argument_value = js_undefined(); + } + + vm.assign(param, argument_value, global_object(), true, vm.current_scope()); + }); - if (!interpreter) { - local_interpreter = Interpreter::create_with_existing_global_object(global_object()); - interpreter = local_interpreter.ptr(); - } + if (vm.exception()) + return; + } + }; + + if (bytecode_interpreter) { + prepare_arguments(); + auto block = Bytecode::Generator::generate(m_body); + VERIFY(block); + dbgln("Compiled Bytecode::Block for function '{}':", m_name); + block->dump(); + return bytecode_interpreter->run(*block); + } else { + OwnPtr<Interpreter> local_interpreter; + ast_interpreter = vm.interpreter_if_exists(); + + if (!ast_interpreter) { + local_interpreter = Interpreter::create_with_existing_global_object(global_object()); + ast_interpreter = local_interpreter.ptr(); + } - VM::InterpreterExecutionScope scope(*interpreter); - - auto& call_frame_args = vm.call_frame().arguments; - for (size_t i = 0; i < m_parameters.size(); ++i) { - auto& parameter = m_parameters[i]; - parameter.binding.visit( - [&](const auto& param) { - Value argument_value; - if (parameter.is_rest) { - auto* array = Array::create(global_object()); - for (size_t rest_index = i; rest_index < call_frame_args.size(); ++rest_index) - array->indexed_properties().append(call_frame_args[rest_index]); - argument_value = move(array); - } else if (i < call_frame_args.size() && !call_frame_args[i].is_undefined()) { - argument_value = call_frame_args[i]; - } else if (parameter.default_value) { - argument_value = parameter.default_value->execute(*interpreter, global_object()); - if (vm.exception()) - return; - } else { - argument_value = js_undefined(); - } - - vm.assign(param, argument_value, global_object(), true, vm.current_scope()); - }); + VM::InterpreterExecutionScope scope(*ast_interpreter); + prepare_arguments(); if (vm.exception()) return {}; - } - return interpreter->execute_statement(global_object(), m_body, ScopeType::Function); + return ast_interpreter->execute_statement(global_object(), m_body, ScopeType::Function); + } } Value ScriptFunction::call() |