diff --git a/Meta/Lagom/Wasm/js_repl.cpp b/Meta/Lagom/Wasm/js_repl.cpp index 9d77507ca58..3e7a665f97e 100644 --- a/Meta/Lagom/Wasm/js_repl.cpp +++ b/Meta/Lagom/Wasm/js_repl.cpp @@ -103,7 +103,6 @@ private: static bool s_dump_ast = false; static bool s_run_bytecode = false; -static bool s_opt_bytecode = false; static bool s_as_module = false; static bool s_print_last_result = false; @@ -120,33 +119,9 @@ static ErrorOr parse_and_run(JS::Interpreter& interpreter, StringView sour if (s_dump_ast) script_or_module->parse_node().dump(0); - if (JS::Bytecode::g_dump_bytecode || s_run_bytecode) { - auto executable_result = JS::Bytecode::Generator::generate(script_or_module->parse_node()); - if (executable_result.is_error()) { - result = g_vm->throw_completion(TRY(executable_result.error().to_string())); - return ReturnEarly::No; - } - - auto executable = executable_result.release_value(); - executable->name = source_name; - if (s_opt_bytecode) { - auto& passes = JS::Bytecode::Interpreter::optimization_pipeline(); - passes.perform(*executable); - } - - if (JS::Bytecode::g_dump_bytecode) - executable->dump(); - - if (s_run_bytecode) { - JS::Bytecode::Interpreter bytecode_interpreter(interpreter.realm()); - auto result_or_error = bytecode_interpreter.run_and_return_frame(*executable, nullptr); - if (result_or_error.value.is_error()) - result = result_or_error.value.release_error(); - else - result = result_or_error.frame->registers[0]; - } else { - return ReturnEarly::Yes; - } + if (s_run_bytecode) { + JS::Bytecode::Interpreter bytecode_interpreter(interpreter.realm()); + result = bytecode_interpreter.run(*script_or_module); } else { result = interpreter.run(*script_or_module); } diff --git a/Tests/LibJS/test-bytecode-js.cpp b/Tests/LibJS/test-bytecode-js.cpp index ed2641a1edf..1ab808e2e14 100644 --- a/Tests/LibJS/test-bytecode-js.cpp +++ b/Tests/LibJS/test-bytecode-js.cpp @@ -22,7 +22,7 @@ #define EXPECT_NO_EXCEPTION(executable) \ auto executable = MUST(JS::Bytecode::Generator::generate(program)); \ - auto result = bytecode_interpreter.run(*executable); \ + auto result = bytecode_interpreter.run(*script); \ if (result.is_error()) { \ FAIL("unexpected exception"); \ dbgln("Error: {}", MUST(result.throw_completion().value()->to_deprecated_string(vm))); \ @@ -113,11 +113,8 @@ TEST_CASE(loading_multiple_files) auto test_file_script = MUST(JS::Script::parse( "if (f() !== 'hello') throw new Exception('failed'); "sv, ast_interpreter->realm())); - auto const& test_file_program = test_file_script->parse_node(); - - auto executable = MUST(JS::Bytecode::Generator::generate(test_file_program)); // TODO: This could be TRY_OR_FAIL(), if someone implements Formatter. - MUST(bytecode_interpreter.run(*executable)); + MUST(bytecode_interpreter.run(test_file_script)); } } diff --git a/Tests/LibJS/test262-runner.cpp b/Tests/LibJS/test262-runner.cpp index bba6bc56af6..38b06f14e27 100644 --- a/Tests/LibJS/test262-runner.cpp +++ b/Tests/LibJS/test262-runner.cpp @@ -79,35 +79,10 @@ static Result parse_program(JS::Realm& realm, template static Result run_program(InterpreterT& interpreter, ScriptOrModuleProgram& program) { - auto result = JS::ThrowCompletionOr { JS::js_undefined() }; - if constexpr (IsSame) { - result = program.visit( - [&](auto& visitor) { - return interpreter.run(*visitor); - }); - } else { - auto program_node = program.visit( - [](auto& visitor) -> NonnullRefPtr { - return visitor->parse_node(); - }); - - auto& vm = interpreter.vm(); - - if (auto unit_result = JS::Bytecode::Generator::generate(program_node); unit_result.is_error()) { - if (auto error_string = unit_result.error().to_string(); error_string.is_error()) - result = vm.template throw_completion(vm.error_message(JS::VM::ErrorMessage::OutOfMemory)); - else if (error_string = String::formatted("TODO({})", error_string.value()); error_string.is_error()) - result = vm.template throw_completion(vm.error_message(JS::VM::ErrorMessage::OutOfMemory)); - else - result = JS::throw_completion(JS::InternalError::create(interpreter.realm(), error_string.release_value())); - } else { - auto unit = unit_result.release_value(); - auto optimization_level = s_enable_bytecode_optimizations ? JS::Bytecode::Interpreter::OptimizationLevel::Optimize : JS::Bytecode::Interpreter::OptimizationLevel::Default; - auto& passes = JS::Bytecode::Interpreter::optimization_pipeline(optimization_level); - passes.perform(*unit); - result = interpreter.run(*unit); - } - } + auto result = program.visit( + [&](auto& visitor) { + return interpreter.run(*visitor); + }); if (result.is_error()) { auto error_value = *result.throw_completion().value(); diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 13f78b2088f..9898cac2cc0 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -70,182 +70,7 @@ Bytecode::CodeGenerationErrorOr ScopeNode::generate_bytecode(Bytecode::Gen return {}; } else if (is(*this)) { - // Perform the steps of GlobalDeclarationInstantiation. - generator.begin_variable_scope(Bytecode::Generator::BindingMode::Global, Bytecode::Generator::SurroundingScopeKind::Global); - pushed_scope_count++; - - // 1. Let lexNames be the LexicallyDeclaredNames of script. - // 2. Let varNames be the VarDeclaredNames of script. - // 3. For each element name of lexNames, do - (void)for_each_lexically_declared_name([&](auto const& name) -> ThrowCompletionOr { - auto identifier = generator.intern_identifier(name); - // a. If env.HasVarDeclaration(name) is true, throw a SyntaxError exception. - // b. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. - if (generator.has_binding(identifier)) { - // FIXME: Throw an actual SyntaxError instance. - generator.emit(generator.intern_string(DeprecatedString::formatted("SyntaxError: toplevel variable already declared: {}", name))); - generator.emit(); - return {}; - } - - // FIXME: c. If hasRestrictedGlobalProperty is true, throw a SyntaxError exception. - // d. If hasRestrictedGlobal is true, throw a SyntaxError exception. - return {}; - }); - - // 4. For each element name of varNames, do - (void)for_each_var_declared_name([&](auto const& name) -> ThrowCompletionOr { - auto identifier = generator.intern_identifier(name); - // a. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. - if (generator.has_binding(identifier)) { - // FIXME: Throw an actual SyntaxError instance. - generator.emit(generator.intern_string(DeprecatedString::formatted("SyntaxError: toplevel variable already declared: {}", name))); - generator.emit(); - } - return {}; - }); - - // 5. Let varDeclarations be the VarScopedDeclarations of script. - // 6. Let functionsToInitialize be a new empty List. - Vector functions_to_initialize; - - // 7. Let declaredFunctionNames be a new empty List. - HashTable declared_function_names; - - // 8. For each element d of varDeclarations, in reverse List order, do - (void)for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) -> ThrowCompletionOr { - // a. If d is neither a VariableDeclaration nor a ForBinding nor a BindingIdentifier, then - // i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration. - // Note: This is checked in for_each_var_function_declaration_in_reverse_order. - // ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used. - // iii. Let fn be the sole element of the BoundNames of d. - - // iv. If fn is not an element of declaredFunctionNames, then - if (declared_function_names.set(function.name()) != AK::HashSetResult::InsertedNewEntry) - return {}; - - // FIXME: 1. Let fnDefinable be ? env.CanDeclareGlobalFunction(fn). - // FIXME: 2. If fnDefinable is false, throw a TypeError exception. - - // 3. Append fn to declaredFunctionNames. - // Note: Already done in step iv. above. - - // 4. Insert d as the first element of functionsToInitialize. - functions_to_initialize.prepend(function); - return {}; - }); - - // 9. Let declaredVarNames be a new empty List. - HashTable declared_var_names; - - // 10. For each element d of varDeclarations, do - (void)for_each_var_scoped_variable_declaration([&](Declaration const& declaration) { - // a. If d is a VariableDeclaration, a ForBinding, or a BindingIdentifier, then - // Note: This is done in for_each_var_scoped_variable_declaration. - - // i. For each String vn of the BoundNames of d, do - return declaration.for_each_bound_name([&](auto const& name) -> ThrowCompletionOr { - // 1. If vn is not an element of declaredFunctionNames, then - if (declared_function_names.contains(name)) - return {}; - - // FIXME: a. Let vnDefinable be ? env.CanDeclareGlobalVar(vn). - // FIXME: b. If vnDefinable is false, throw a TypeError exception. - - // c. If vn is not an element of declaredVarNames, then - // i. Append vn to declaredVarNames. - declared_var_names.set(name); - return {}; - }); - }); - - // 11. NOTE: No abnormal terminations occur after this algorithm step if the global object is an ordinary object. However, if the global object is a Proxy exotic object it may exhibit behaviours that cause abnormal terminations in some of the following steps. - // 12. NOTE: Annex B.3.2.2 adds additional steps at this point. - - // 12. Let strict be IsStrict of script. - // 13. If strict is false, then - if (!verify_cast(*this).is_strict_mode()) { - // a. Let declaredFunctionOrVarNames be the list-concatenation of declaredFunctionNames and declaredVarNames. - // b. For each FunctionDeclaration f that is directly contained in the StatementList of a Block, CaseClause, or DefaultClause Contained within script, do - (void)for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) { - // i. Let F be StringValue of the BindingIdentifier of f. - auto& function_name = function_declaration.name(); - - // ii. If replacing the FunctionDeclaration f with a VariableStatement that has F as a BindingIdentifier would not produce any Early Errors for script, then - // Note: This step is already performed during parsing and for_each_function_hoistable_with_annexB_extension so this always passes here. - - // 1. If env.HasLexicalDeclaration(F) is false, then - auto index = generator.intern_identifier(function_name); - if (generator.has_binding(index, Bytecode::Generator::BindingMode::Lexical)) - return; - - // FIXME: a. Let fnDefinable be ? env.CanDeclareGlobalVar(F). - // b. If fnDefinable is true, then - // i. NOTE: A var binding for F is only instantiated here if it is neither a VarDeclaredName nor the name of another FunctionDeclaration. - // ii. If declaredFunctionOrVarNames does not contain F, then - if (!declared_function_names.contains(function_name) && !declared_var_names.contains(function_name)) { - // i. Perform ? env.CreateGlobalVarBinding(F, false). - generator.emit(index, Bytecode::Op::EnvironmentMode::Var, false, true); - - // ii. Append F to declaredFunctionOrVarNames. - declared_function_names.set(function_name); - } - - // iii. When the FunctionDeclaration f is evaluated, perform the following steps in place of the FunctionDeclaration Evaluation algorithm provided in 15.2.6: - // i. Let genv be the running execution context's VariableEnvironment. - // ii. Let benv be the running execution context's LexicalEnvironment. - // iii. Let fobj be ! benv.GetBindingValue(F, false). - // iv. Perform ? genv.SetMutableBinding(F, fobj, false). - // v. Return unused. - function_declaration.set_should_do_additional_annexB_steps(); - }); - } - - // 15. For each element d of lexDeclarations, do - (void)for_each_lexically_scoped_declaration([&](Declaration const& declaration) -> ThrowCompletionOr { - // a. NOTE: Lexically declared names are only instantiated here but not initialized. - // b. For each element dn of the BoundNames of d, do - return declaration.for_each_bound_name([&](auto const& name) -> ThrowCompletionOr { - auto identifier = generator.intern_identifier(name); - // i. If IsConstantDeclaration of d is true, then - generator.register_binding(identifier); - if (declaration.is_constant_declaration()) { - // 1. Perform ? env.CreateImmutableBinding(dn, true). - generator.emit(identifier, Bytecode::Op::EnvironmentMode::Lexical, true); - } else { - // ii. Else, - // 1. Perform ? env.CreateMutableBinding(dn, false). - generator.emit(identifier, Bytecode::Op::EnvironmentMode::Lexical, false); - } - - return {}; - }); - }); - - // 16. For each Parse Node f of functionsToInitialize, do - for (auto& function_declaration : functions_to_initialize) { - // FIXME: Do this more correctly. - // a. Let fn be the sole element of the BoundNames of f. - // b. Let fo be InstantiateFunctionObject of f with arguments env and privateEnv. - generator.emit(function_declaration); - - // c. Perform ? env.CreateGlobalFunctionBinding(fn, fo, false). - auto const& name = function_declaration.name(); - auto index = generator.intern_identifier(name); - if (!generator.has_binding(index)) { - generator.register_binding(index, Bytecode::Generator::BindingMode::Var); - generator.emit(index, Bytecode::Op::EnvironmentMode::Lexical, false); - } - generator.emit(index, Bytecode::Op::SetVariable::InitializationMode::Initialize); - } - - // 17. For each String vn of declaredVarNames, do - for (auto& var_name : declared_var_names) { - // a. Perform ? env.CreateGlobalVarBinding(vn, false). - auto index = generator.intern_identifier(var_name); - generator.register_binding(index, Bytecode::Generator::BindingMode::Var); - generator.emit(index, Bytecode::Op::EnvironmentMode::Var, false, true); - } + // GlobalDeclarationInstantiation is handled by the C++ AO. } else { // FunctionDeclarationInstantiation is handled by the C++ AO. } diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.cpp b/Userland/Libraries/LibJS/Bytecode/Generator.cpp index 714db25c365..586809eeb74 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Generator.cpp @@ -60,7 +60,8 @@ CodeGenerationErrorOr> Generator::generate(ASTNode con .string_table = move(generator.m_string_table), .identifier_table = move(generator.m_identifier_table), .number_of_registers = generator.m_next_register, - .is_strict_mode = is_strict_mode }); + .is_strict_mode = is_strict_mode, + }); } void Generator::grow(size_t additional_size) diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp index c6dfb3aa48b..9574711384b 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -39,6 +40,141 @@ Interpreter::~Interpreter() s_current = nullptr; } +// 16.1.6 ScriptEvaluation ( scriptRecord ), https://tc39.es/ecma262/#sec-runtime-semantics-scriptevaluation +ThrowCompletionOr Interpreter::run(Script& script_record, JS::GCPtr lexical_environment_override) +{ + auto& vm = this->vm(); + + // 1. Let globalEnv be scriptRecord.[[Realm]].[[GlobalEnv]]. + auto& global_environment = script_record.realm().global_environment(); + + // 2. Let scriptContext be a new ECMAScript code execution context. + ExecutionContext script_context(vm.heap()); + + // 3. Set the Function of scriptContext to null. + // NOTE: This was done during execution context construction. + + // 4. Set the Realm of scriptContext to scriptRecord.[[Realm]]. + script_context.realm = &script_record.realm(); + + // 5. Set the ScriptOrModule of scriptContext to scriptRecord. + script_context.script_or_module = NonnullGCPtr