Browse Source

LibJS: Implement default values for function parameters in BC

Ali Mohammad Pur 3 years ago
parent
commit
d75cf27e02

+ 40 - 17
Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp

@@ -426,8 +426,12 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
     // The spec makes an iterator here to do IteratorBindingInitialization but we just do it manually
     auto& execution_context_arguments = vm.running_execution_context().arguments;
 
+    size_t default_parameter_index = 0;
     for (size_t i = 0; i < m_formal_parameters.size(); ++i) {
         auto& parameter = m_formal_parameters[i];
+        if (parameter.default_value)
+            ++default_parameter_index;
+
         TRY(parameter.binding.visit(
             [&](auto const& param) -> ThrowCompletionOr<void> {
                 Value argument_value;
@@ -439,9 +443,15 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
                 } else if (i < execution_context_arguments.size() && !execution_context_arguments[i].is_undefined()) {
                     argument_value = execution_context_arguments[i];
                 } else if (parameter.default_value) {
-                    // FIXME: Support default arguments in the bytecode world!
-                    if (interpreter)
+                    if (auto* bytecode_interpreter = Bytecode::Interpreter::current()) {
+                        auto value_and_frame = bytecode_interpreter->run_and_return_frame(*m_default_parameter_bytecode_executables[default_parameter_index - 1], nullptr);
+                        if (value_and_frame.value.is_error())
+                            return value_and_frame.value.release_error();
+                        // Resulting value is in the accumulator.
+                        argument_value = value_and_frame.frame->registers.at(0);
+                    } else if (interpreter) {
                         argument_value = TRY(parameter.default_value->execute(*interpreter, global_object())).release_value();
+                    }
                 } else {
                     argument_value = js_undefined();
                 }
@@ -769,24 +779,37 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
         return vm.throw_completion<InternalError>(global_object(), ErrorType::NotImplemented, "Async Generator function execution");
 
     if (bytecode_interpreter) {
-        // FIXME: pass something to evaluate default arguments with
-        TRY(function_declaration_instantiation(nullptr));
         if (!m_bytecode_executable) {
-            auto executable_result = JS::Bytecode::Generator::generate(m_ecmascript_code, m_kind);
-            if (executable_result.is_error())
-                return vm.throw_completion<InternalError>(bytecode_interpreter->global_object(), ErrorType::NotImplemented, executable_result.error().to_string());
-
-            m_bytecode_executable = executable_result.release_value();
-            m_bytecode_executable->name = m_name;
-            auto& passes = JS::Bytecode::Interpreter::optimization_pipeline();
-            passes.perform(*m_bytecode_executable);
-            if constexpr (JS_BYTECODE_DEBUG) {
-                dbgln("Optimisation passes took {}us", passes.elapsed());
-                dbgln("Compiled Bytecode::Block for function '{}':", m_name);
+            auto compile = [&](auto& node, auto kind, auto name) -> ThrowCompletionOr<NonnullOwnPtr<Bytecode::Executable>> {
+                auto executable_result = JS::Bytecode::Generator::generate(node, kind);
+                if (executable_result.is_error())
+                    return vm.throw_completion<InternalError>(bytecode_interpreter->global_object(), ErrorType::NotImplemented, executable_result.error().to_string());
+
+                auto bytecode_executable = executable_result.release_value();
+                bytecode_executable->name = name;
+                auto& passes = JS::Bytecode::Interpreter::optimization_pipeline();
+                passes.perform(*bytecode_executable);
+                if constexpr (JS_BYTECODE_DEBUG) {
+                    dbgln("Optimisation passes took {}us", passes.elapsed());
+                    dbgln("Compiled Bytecode::Block for function '{}':", m_name);
+                }
+                if (JS::Bytecode::g_dump_bytecode)
+                    bytecode_executable->dump();
+
+                return bytecode_executable;
+            };
+
+            m_bytecode_executable = TRY(compile(*m_ecmascript_code, m_kind, m_name));
+
+            size_t default_parameter_index = 0;
+            for (auto& parameter : m_formal_parameters) {
+                if (!parameter.default_value)
+                    continue;
+                auto executable = TRY(compile(*parameter.default_value, FunctionKind::Normal, String::formatted("default parameter #{} for {}", default_parameter_index, m_name)));
+                m_default_parameter_bytecode_executables.append(move(executable));
             }
-            if (JS::Bytecode::g_dump_bytecode)
-                m_bytecode_executable->dump();
         }
+        TRY(function_declaration_instantiation(nullptr));
         auto result_and_frame = bytecode_interpreter->run_and_return_frame(*m_bytecode_executable, nullptr);
 
         VERIFY(result_and_frame.frame != nullptr);

+ 1 - 0
Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h

@@ -109,6 +109,7 @@ private:
 
     FlyString m_name;
     OwnPtr<Bytecode::Executable> m_bytecode_executable;
+    Vector<OwnPtr<Bytecode::Executable>> m_default_parameter_bytecode_executables;
     i32 m_function_length { 0 };
 
     // Internal Slots of ECMAScript Function Objects, https://tc39.es/ecma262/#table-internal-slots-of-ecmascript-function-objects