mirror of
				https://github.com/LadybirdBrowser/ladybird.git
				synced 2025-10-31 05:10:57 +00:00 
			
		
		
		
	LibJS/Bytecode: Leave GlobalDeclarationInstantiation in C++
Don't try to implement this AO in bytecode. Instead, the bytecode Interpreter class now has a run() API with the same inputs as the AST interpreter. It sets up the necessary environments etc, including invoking the GlobalDeclarationInstantiation AO.
This commit is contained in:
		
							parent
							
								
									32d9c8e3ca
								
							
						
					
					
						commit
						d063f35afd
					
				
				
				Notes:
				
					sideshowbarker
				
				2024-07-18 01:43:16 +09:00 
				
			
			Author: https://github.com/awesomekling
Commit: d063f35afd
Pull-request: https://github.com/SerenityOS/serenity/pull/19408
Issue: https://github.com/SerenityOS/serenity/issues/15210
			
					 12 changed files with 172 additions and 284 deletions
				
			
		|  | @ -103,7 +103,6 @@ private: | ||||||
| 
 | 
 | ||||||
| static bool s_dump_ast = false; | static bool s_dump_ast = false; | ||||||
| static bool s_run_bytecode = false; | static bool s_run_bytecode = false; | ||||||
| static bool s_opt_bytecode = false; |  | ||||||
| static bool s_as_module = false; | static bool s_as_module = false; | ||||||
| static bool s_print_last_result = false; | static bool s_print_last_result = false; | ||||||
| 
 | 
 | ||||||
|  | @ -120,33 +119,9 @@ static ErrorOr<bool> parse_and_run(JS::Interpreter& interpreter, StringView sour | ||||||
|         if (s_dump_ast) |         if (s_dump_ast) | ||||||
|             script_or_module->parse_node().dump(0); |             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<JS::InternalError>(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) { |         if (s_run_bytecode) { | ||||||
|             JS::Bytecode::Interpreter bytecode_interpreter(interpreter.realm()); |             JS::Bytecode::Interpreter bytecode_interpreter(interpreter.realm()); | ||||||
|                 auto result_or_error = bytecode_interpreter.run_and_return_frame(*executable, nullptr); |             result = bytecode_interpreter.run(*script_or_module); | ||||||
|                 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; |  | ||||||
|             } |  | ||||||
|         } else { |         } else { | ||||||
|             result = interpreter.run(*script_or_module); |             result = interpreter.run(*script_or_module); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -22,7 +22,7 @@ | ||||||
| 
 | 
 | ||||||
| #define EXPECT_NO_EXCEPTION(executable)                                                        \ | #define EXPECT_NO_EXCEPTION(executable)                                                        \ | ||||||
|     auto executable = MUST(JS::Bytecode::Generator::generate(program));                        \ |     auto executable = MUST(JS::Bytecode::Generator::generate(program));                        \ | ||||||
|     auto result = bytecode_interpreter.run(*executable);                                       \ |     auto result = bytecode_interpreter.run(*script);                                           \ | ||||||
|     if (result.is_error()) {                                                                   \ |     if (result.is_error()) {                                                                   \ | ||||||
|         FAIL("unexpected exception");                                                          \ |         FAIL("unexpected exception");                                                          \ | ||||||
|         dbgln("Error: {}", MUST(result.throw_completion().value()->to_deprecated_string(vm))); \ |         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( |         auto test_file_script = MUST(JS::Script::parse( | ||||||
|             "if (f() !== 'hello') throw new Exception('failed'); "sv, ast_interpreter->realm())); |             "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<JS::Completion>.
 |         // TODO: This could be TRY_OR_FAIL(), if someone implements Formatter<JS::Completion>.
 | ||||||
|         MUST(bytecode_interpreter.run(*executable)); |         MUST(bytecode_interpreter.run(test_file_script)); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -79,35 +79,10 @@ static Result<ScriptOrModuleProgram, TestError> parse_program(JS::Realm& realm, | ||||||
| template<typename InterpreterT> | template<typename InterpreterT> | ||||||
| static Result<void, TestError> run_program(InterpreterT& interpreter, ScriptOrModuleProgram& program) | static Result<void, TestError> run_program(InterpreterT& interpreter, ScriptOrModuleProgram& program) | ||||||
| { | { | ||||||
|     auto result = JS::ThrowCompletionOr<JS::Value> { JS::js_undefined() }; |     auto result = program.visit( | ||||||
|     if constexpr (IsSame<InterpreterT, JS::Interpreter>) { |  | ||||||
|         result = program.visit( |  | ||||||
|         [&](auto& visitor) { |         [&](auto& visitor) { | ||||||
|             return interpreter.run(*visitor); |             return interpreter.run(*visitor); | ||||||
|         }); |         }); | ||||||
|     } else { |  | ||||||
|         auto program_node = program.visit( |  | ||||||
|             [](auto& visitor) -> NonnullRefPtr<JS::Program const> { |  | ||||||
|                 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<JS::InternalError>(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<JS::InternalError>(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); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     if (result.is_error()) { |     if (result.is_error()) { | ||||||
|         auto error_value = *result.throw_completion().value(); |         auto error_value = *result.throw_completion().value(); | ||||||
|  |  | ||||||
|  | @ -70,182 +70,7 @@ Bytecode::CodeGenerationErrorOr<void> ScopeNode::generate_bytecode(Bytecode::Gen | ||||||
|             return {}; |             return {}; | ||||||
| 
 | 
 | ||||||
|     } else if (is<Program>(*this)) { |     } else if (is<Program>(*this)) { | ||||||
|         // Perform the steps of GlobalDeclarationInstantiation.
 |         // GlobalDeclarationInstantiation is handled by the C++ AO.
 | ||||||
|         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<void> { |  | ||||||
|             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<Bytecode::Op::NewString>(generator.intern_string(DeprecatedString::formatted("SyntaxError: toplevel variable already declared: {}", name))); |  | ||||||
|                 generator.emit<Bytecode::Op::Throw>(); |  | ||||||
|                 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<void> { |  | ||||||
|             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<Bytecode::Op::NewString>(generator.intern_string(DeprecatedString::formatted("SyntaxError: toplevel variable already declared: {}", name))); |  | ||||||
|                 generator.emit<Bytecode::Op::Throw>(); |  | ||||||
|             } |  | ||||||
|             return {}; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         // 5. Let varDeclarations be the VarScopedDeclarations of script.
 |  | ||||||
|         // 6. Let functionsToInitialize be a new empty List.
 |  | ||||||
|         Vector<FunctionDeclaration const&> functions_to_initialize; |  | ||||||
| 
 |  | ||||||
|         // 7. Let declaredFunctionNames be a new empty List.
 |  | ||||||
|         HashTable<DeprecatedFlyString> 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<void> { |  | ||||||
|             // 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<DeprecatedFlyString> 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<void> { |  | ||||||
|                 // 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<Program>(*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<Bytecode::Op::CreateVariable>(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<void> { |  | ||||||
|             // 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<void> { |  | ||||||
|                 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<Bytecode::Op::CreateVariable>(identifier, Bytecode::Op::EnvironmentMode::Lexical, true); |  | ||||||
|                 } else { |  | ||||||
|                     // ii. Else,
 |  | ||||||
|                     // 1. Perform ? env.CreateMutableBinding(dn, false).
 |  | ||||||
|                     generator.emit<Bytecode::Op::CreateVariable>(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<Bytecode::Op::NewFunction>(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<Bytecode::Op::CreateVariable>(index, Bytecode::Op::EnvironmentMode::Lexical, false); |  | ||||||
|             } |  | ||||||
|             generator.emit<Bytecode::Op::SetVariable>(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<Bytecode::Op::CreateVariable>(index, Bytecode::Op::EnvironmentMode::Var, false, true); |  | ||||||
|         } |  | ||||||
|     } else { |     } else { | ||||||
|         // FunctionDeclarationInstantiation is handled by the C++ AO.
 |         // FunctionDeclarationInstantiation is handled by the C++ AO.
 | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -60,7 +60,8 @@ CodeGenerationErrorOr<NonnullOwnPtr<Executable>> Generator::generate(ASTNode con | ||||||
|         .string_table = move(generator.m_string_table), |         .string_table = move(generator.m_string_table), | ||||||
|         .identifier_table = move(generator.m_identifier_table), |         .identifier_table = move(generator.m_identifier_table), | ||||||
|         .number_of_registers = generator.m_next_register, |         .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) | void Generator::grow(size_t additional_size) | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| 
 | 
 | ||||||
| #include <AK/Debug.h> | #include <AK/Debug.h> | ||||||
| #include <AK/TemporaryChange.h> | #include <AK/TemporaryChange.h> | ||||||
|  | #include <LibJS/AST.h> | ||||||
| #include <LibJS/Bytecode/BasicBlock.h> | #include <LibJS/Bytecode/BasicBlock.h> | ||||||
| #include <LibJS/Bytecode/Instruction.h> | #include <LibJS/Bytecode/Instruction.h> | ||||||
| #include <LibJS/Bytecode/Interpreter.h> | #include <LibJS/Bytecode/Interpreter.h> | ||||||
|  | @ -39,6 +40,141 @@ Interpreter::~Interpreter() | ||||||
|     s_current = nullptr; |     s_current = nullptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // 16.1.6 ScriptEvaluation ( scriptRecord ), https://tc39.es/ecma262/#sec-runtime-semantics-scriptevaluation
 | ||||||
|  | ThrowCompletionOr<Value> Interpreter::run(Script& script_record, JS::GCPtr<Environment> 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<Script>(script_record); | ||||||
|  | 
 | ||||||
|  |     // 6. Set the VariableEnvironment of scriptContext to globalEnv.
 | ||||||
|  |     script_context.variable_environment = &global_environment; | ||||||
|  | 
 | ||||||
|  |     // 7. Set the LexicalEnvironment of scriptContext to globalEnv.
 | ||||||
|  |     script_context.lexical_environment = &global_environment; | ||||||
|  | 
 | ||||||
|  |     // Non-standard: Override the lexical environment if requested.
 | ||||||
|  |     if (lexical_environment_override) | ||||||
|  |         script_context.lexical_environment = lexical_environment_override; | ||||||
|  | 
 | ||||||
|  |     // 8. Set the PrivateEnvironment of scriptContext to null.
 | ||||||
|  | 
 | ||||||
|  |     // NOTE: This isn't in the spec, but we require it.
 | ||||||
|  |     script_context.is_strict_mode = script_record.parse_node().is_strict_mode(); | ||||||
|  | 
 | ||||||
|  |     // FIXME: 9. Suspend the currently running execution context.
 | ||||||
|  | 
 | ||||||
|  |     // 10. Push scriptContext onto the execution context stack; scriptContext is now the running execution context.
 | ||||||
|  |     TRY(vm.push_execution_context(script_context, {})); | ||||||
|  | 
 | ||||||
|  |     // 11. Let script be scriptRecord.[[ECMAScriptCode]].
 | ||||||
|  |     auto& script = script_record.parse_node(); | ||||||
|  | 
 | ||||||
|  |     // 12. Let result be Completion(GlobalDeclarationInstantiation(script, globalEnv)).
 | ||||||
|  |     auto instantiation_result = script.global_declaration_instantiation(vm, global_environment); | ||||||
|  |     Completion result = instantiation_result.is_throw_completion() ? instantiation_result.throw_completion() : normal_completion({}); | ||||||
|  | 
 | ||||||
|  |     // 13. If result.[[Type]] is normal, then
 | ||||||
|  |     if (result.type() == Completion::Type::Normal) { | ||||||
|  |         auto executable_result = JS::Bytecode::Generator::generate(script); | ||||||
|  | 
 | ||||||
|  |         if (executable_result.is_error()) { | ||||||
|  |             if (auto error_string = executable_result.error().to_string(); error_string.is_error()) | ||||||
|  |                 result = vm.template throw_completion<JS::InternalError>(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<JS::InternalError>(vm.error_message(JS::VM::ErrorMessage::OutOfMemory)); | ||||||
|  |             else | ||||||
|  |                 result = JS::throw_completion(JS::InternalError::create(realm(), error_string.release_value())); | ||||||
|  |         } else { | ||||||
|  |             auto executable = executable_result.release_value(); | ||||||
|  | 
 | ||||||
|  |             if (m_optimizations_enabled) { | ||||||
|  |                 auto& passes = optimization_pipeline(); | ||||||
|  |                 passes.perform(*executable); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (g_dump_bytecode) | ||||||
|  |                 executable->dump(); | ||||||
|  | 
 | ||||||
|  |             // a. Set result to the result of evaluating script.
 | ||||||
|  |             auto result_or_error = 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]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 14. If result.[[Type]] is normal and result.[[Value]] is empty, then
 | ||||||
|  |     if (result.type() == Completion::Type::Normal && !result.value().has_value()) { | ||||||
|  |         // a. Set result to NormalCompletion(undefined).
 | ||||||
|  |         result = normal_completion(js_undefined()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // FIXME: 15. Suspend scriptContext and remove it from the execution context stack.
 | ||||||
|  |     vm.pop_execution_context(); | ||||||
|  | 
 | ||||||
|  |     // 16. Assert: The execution context stack is not empty.
 | ||||||
|  |     VERIFY(!vm.execution_context_stack().is_empty()); | ||||||
|  | 
 | ||||||
|  |     // FIXME: 17. Resume the context that is now on the top of the execution context stack as the running execution context.
 | ||||||
|  | 
 | ||||||
|  |     // At this point we may have already run any queued promise jobs via on_call_stack_emptied,
 | ||||||
|  |     // in which case this is a no-op.
 | ||||||
|  |     // FIXME: These three should be moved out of Interpreter::run and give the host an option to run these, as it's up to the host when these get run.
 | ||||||
|  |     //        https://tc39.es/ecma262/#sec-jobs for jobs and https://tc39.es/ecma262/#_ref_3508 for ClearKeptObjects
 | ||||||
|  |     //        finish_execution_generation is particularly an issue for LibWeb, as the HTML spec wants to run it specifically after performing a microtask checkpoint.
 | ||||||
|  |     //        The promise and registry cleanup queues don't cause LibWeb an issue, as LibWeb overrides the hooks that push onto these queues.
 | ||||||
|  |     vm.run_queued_promise_jobs(); | ||||||
|  | 
 | ||||||
|  |     vm.run_queued_finalization_registry_cleanup_jobs(); | ||||||
|  | 
 | ||||||
|  |     vm.finish_execution_generation(); | ||||||
|  | 
 | ||||||
|  |     // 18. Return ? result.
 | ||||||
|  |     if (result.is_abrupt()) { | ||||||
|  |         VERIFY(result.type() == Completion::Type::Throw); | ||||||
|  |         return result.release_error(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     VERIFY(result.value().has_value()); | ||||||
|  |     return *result.value(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ThrowCompletionOr<Value> Interpreter::run(SourceTextModule& module) | ||||||
|  | { | ||||||
|  |     // FIXME: This is not a entry point as defined in the spec, but is convenient.
 | ||||||
|  |     //        To avoid work we use link_and_eval_module however that can already be
 | ||||||
|  |     //        dangerous if the vm loaded other modules.
 | ||||||
|  |     auto& vm = this->vm(); | ||||||
|  | 
 | ||||||
|  |     TRY(vm.link_and_eval_module(Badge<Bytecode::Interpreter> {}, module)); | ||||||
|  | 
 | ||||||
|  |     vm.run_queued_promise_jobs(); | ||||||
|  | 
 | ||||||
|  |     vm.run_queued_finalization_registry_cleanup_jobs(); | ||||||
|  | 
 | ||||||
|  |     return js_undefined(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void Interpreter::set_optimizations_enabled(bool enabled) | ||||||
|  | { | ||||||
|  |     m_optimizations_enabled = enabled; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable const& executable, BasicBlock const* entry_point, RegisterWindow* in_frame) | Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Executable const& executable, BasicBlock const* entry_point, RegisterWindow* in_frame) | ||||||
| { | { | ||||||
|     dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter will run unit {:p}", &executable); |     dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter will run unit {:p}", &executable); | ||||||
|  |  | ||||||
|  | @ -36,6 +36,11 @@ public: | ||||||
|     Realm& realm() { return m_realm; } |     Realm& realm() { return m_realm; } | ||||||
|     VM& vm() { return m_vm; } |     VM& vm() { return m_vm; } | ||||||
| 
 | 
 | ||||||
|  |     void set_optimizations_enabled(bool); | ||||||
|  | 
 | ||||||
|  |     ThrowCompletionOr<Value> run(Script&, JS::GCPtr<Environment> lexical_environment_override = nullptr); | ||||||
|  |     ThrowCompletionOr<Value> run(SourceTextModule&); | ||||||
|  | 
 | ||||||
|     ThrowCompletionOr<Value> run(Bytecode::Executable const& executable, Bytecode::BasicBlock const* entry_point = nullptr) |     ThrowCompletionOr<Value> run(Bytecode::Executable const& executable, Bytecode::BasicBlock const* entry_point = nullptr) | ||||||
|     { |     { | ||||||
|         auto value_and_frame = run_and_return_frame(executable, entry_point); |         auto value_and_frame = run_and_return_frame(executable, entry_point); | ||||||
|  | @ -120,6 +125,7 @@ private: | ||||||
|     OwnPtr<JS::Interpreter> m_ast_interpreter; |     OwnPtr<JS::Interpreter> m_ast_interpreter; | ||||||
|     BasicBlock const* m_current_block { nullptr }; |     BasicBlock const* m_current_block { nullptr }; | ||||||
|     InstructionStreamIterator* m_pc { nullptr }; |     InstructionStreamIterator* m_pc { nullptr }; | ||||||
|  |     bool m_optimizations_enabled { false }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| extern bool g_dump_bytecode; | extern bool g_dump_bytecode; | ||||||
|  |  | ||||||
|  | @ -134,7 +134,7 @@ ThrowCompletionOr<Value> Interpreter::run(SourceTextModule& module) | ||||||
| 
 | 
 | ||||||
|     VM::InterpreterExecutionScope scope(*this); |     VM::InterpreterExecutionScope scope(*this); | ||||||
| 
 | 
 | ||||||
|     TRY(vm.link_and_eval_module({}, module)); |     TRY(vm.link_and_eval_module(Badge<JS::Interpreter> {}, module)); | ||||||
| 
 | 
 | ||||||
|     vm.run_queued_promise_jobs(); |     vm.run_queued_promise_jobs(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -792,6 +792,11 @@ ThrowCompletionOr<void> VM::link_and_eval_module(Badge<Interpreter>, SourceTextM | ||||||
|     return link_and_eval_module(module); |     return link_and_eval_module(module); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | ThrowCompletionOr<void> VM::link_and_eval_module(Badge<Bytecode::Interpreter>, SourceTextModule& module) | ||||||
|  | { | ||||||
|  |     return link_and_eval_module(module); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| ThrowCompletionOr<void> VM::link_and_eval_module(Module& module) | ThrowCompletionOr<void> VM::link_and_eval_module(Module& module) | ||||||
| { | { | ||||||
|     auto filename = module.filename(); |     auto filename = module.filename(); | ||||||
|  |  | ||||||
|  | @ -246,6 +246,7 @@ public: | ||||||
| 
 | 
 | ||||||
|     // Do not call this method unless you are sure this is the only and first module to be loaded in this vm.
 |     // Do not call this method unless you are sure this is the only and first module to be loaded in this vm.
 | ||||||
|     ThrowCompletionOr<void> link_and_eval_module(Badge<Interpreter>, SourceTextModule& module); |     ThrowCompletionOr<void> link_and_eval_module(Badge<Interpreter>, SourceTextModule& module); | ||||||
|  |     ThrowCompletionOr<void> link_and_eval_module(Badge<Bytecode::Interpreter>, SourceTextModule& module); | ||||||
| 
 | 
 | ||||||
|     ScriptOrModule get_active_script_or_module() const; |     ScriptOrModule get_active_script_or_module() const; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -361,12 +361,10 @@ inline JSFileResult TestRunner::run_file_test(DeprecatedString const& test_path) | ||||||
|     auto test_script = result.release_value(); |     auto test_script = result.release_value(); | ||||||
| 
 | 
 | ||||||
|     if (g_run_bytecode) { |     if (g_run_bytecode) { | ||||||
|         auto executable = MUST(JS::Bytecode::Generator::generate(test_script->parse_node())); |         g_vm->push_execution_context(global_execution_context); | ||||||
|         executable->name = test_path; |  | ||||||
|         if (JS::Bytecode::g_dump_bytecode) |  | ||||||
|             executable->dump(); |  | ||||||
|         JS::Bytecode::Interpreter bytecode_interpreter(interpreter->realm()); |         JS::Bytecode::Interpreter bytecode_interpreter(interpreter->realm()); | ||||||
|         MUST(bytecode_interpreter.run(*executable)); |         MUST(bytecode_interpreter.run(*test_script)); | ||||||
|  |         g_vm->pop_execution_context(); | ||||||
|     } else { |     } else { | ||||||
|         g_vm->push_execution_context(global_execution_context); |         g_vm->push_execution_context(global_execution_context); | ||||||
|         MUST(interpreter->run(*test_script)); |         MUST(interpreter->run(*test_script)); | ||||||
|  | @ -377,21 +375,14 @@ inline JSFileResult TestRunner::run_file_test(DeprecatedString const& test_path) | ||||||
|     JS::ThrowCompletionOr<JS::Value> top_level_result { JS::js_undefined() }; |     JS::ThrowCompletionOr<JS::Value> top_level_result { JS::js_undefined() }; | ||||||
|     if (file_script.is_error()) |     if (file_script.is_error()) | ||||||
|         return { test_path, file_script.error() }; |         return { test_path, file_script.error() }; | ||||||
|     if (g_run_bytecode) { |  | ||||||
|         auto executable_result = JS::Bytecode::Generator::generate(file_script.value()->parse_node()); |  | ||||||
|         if (!executable_result.is_error()) { |  | ||||||
|             auto executable = executable_result.release_value(); |  | ||||||
|             executable->name = test_path; |  | ||||||
|             if (JS::Bytecode::g_dump_bytecode) |  | ||||||
|                 executable->dump(); |  | ||||||
|             JS::Bytecode::Interpreter bytecode_interpreter(interpreter->realm()); |  | ||||||
|             top_level_result = bytecode_interpreter.run(*executable); |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|     g_vm->push_execution_context(global_execution_context); |     g_vm->push_execution_context(global_execution_context); | ||||||
|  |     if (g_run_bytecode) { | ||||||
|  |         JS::Bytecode::Interpreter bytecode_interpreter(interpreter->realm()); | ||||||
|  |         top_level_result = bytecode_interpreter.run(file_script.value()); | ||||||
|  |     } else { | ||||||
|         top_level_result = interpreter->run(file_script.value()); |         top_level_result = interpreter->run(file_script.value()); | ||||||
|         g_vm->pop_execution_context(); |  | ||||||
|     } |     } | ||||||
|  |     g_vm->pop_execution_context(); | ||||||
| 
 | 
 | ||||||
|     g_vm->push_execution_context(global_execution_context); |     g_vm->push_execution_context(global_execution_context); | ||||||
|     auto test_json = get_test_results(*interpreter); |     auto test_json = get_test_results(*interpreter); | ||||||
|  |  | ||||||
|  | @ -212,34 +212,10 @@ static ErrorOr<bool> parse_and_run(JS::Interpreter& interpreter, StringView sour | ||||||
|         if (s_dump_ast) |         if (s_dump_ast) | ||||||
|             script_or_module->parse_node().dump(0); |             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<JS::InternalError>(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(JS::Bytecode::Interpreter::OptimizationLevel::Optimize); |  | ||||||
|                 passes.perform(*executable); |  | ||||||
|                 dbgln("Optimisation passes took {}us", passes.elapsed()); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (JS::Bytecode::g_dump_bytecode) |  | ||||||
|                 executable->dump(); |  | ||||||
| 
 |  | ||||||
|         if (s_run_bytecode) { |         if (s_run_bytecode) { | ||||||
|             JS::Bytecode::Interpreter bytecode_interpreter(interpreter.realm()); |             JS::Bytecode::Interpreter bytecode_interpreter(interpreter.realm()); | ||||||
|                 auto result_or_error = bytecode_interpreter.run_and_return_frame(*executable, nullptr); |             bytecode_interpreter.set_optimizations_enabled(s_opt_bytecode); | ||||||
|                 if (result_or_error.value.is_error()) |             result = bytecode_interpreter.run(*script_or_module); | ||||||
|                     result = result_or_error.value.release_error(); |  | ||||||
|                 else |  | ||||||
|                     result = result_or_error.frame->registers[0]; |  | ||||||
|             } else { |  | ||||||
|                 return ReturnEarly::Yes; |  | ||||||
|             } |  | ||||||
|         } else { |         } else { | ||||||
|             result = interpreter.run(*script_or_module); |             result = interpreter.run(*script_or_module); | ||||||
|         } |         } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling