mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-11 13:36:13 +03: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: https://github.com/SerenityOS/serenity/commit/d063f35afd Pull-request: https://github.com/SerenityOS/serenity/pull/19408 Issue: https://github.com/SerenityOS/serenity/issues/15210
@ -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) {
|
if (s_run_bytecode) {
|
||||||
auto executable_result = JS::Bytecode::Generator::generate(script_or_module->parse_node());
|
JS::Bytecode::Interpreter bytecode_interpreter(interpreter.realm());
|
||||||
if (executable_result.is_error()) {
|
result = bytecode_interpreter.run(*script_or_module);
|
||||||
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) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
} 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>) {
|
[&](auto& visitor) {
|
||||||
result = program.visit(
|
return interpreter.run(*visitor);
|
||||||
[&](auto& 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() };
|
||||||
|
g_vm->push_execution_context(global_execution_context);
|
||||||
if (g_run_bytecode) {
|
if (g_run_bytecode) {
|
||||||
auto executable_result = JS::Bytecode::Generator::generate(file_script.value()->parse_node());
|
JS::Bytecode::Interpreter bytecode_interpreter(interpreter->realm());
|
||||||
if (!executable_result.is_error()) {
|
top_level_result = bytecode_interpreter.run(file_script.value());
|
||||||
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 {
|
} else {
|
||||||
g_vm->push_execution_context(global_execution_context);
|
|
||||||
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) {
|
if (s_run_bytecode) {
|
||||||
auto executable_result = JS::Bytecode::Generator::generate(script_or_module->parse_node());
|
JS::Bytecode::Interpreter bytecode_interpreter(interpreter.realm());
|
||||||
if (executable_result.is_error()) {
|
bytecode_interpreter.set_optimizations_enabled(s_opt_bytecode);
|
||||||
result = g_vm->throw_completion<JS::InternalError>(TRY(executable_result.error().to_string()));
|
result = bytecode_interpreter.run(*script_or_module);
|
||||||
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) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
result = interpreter.run(*script_or_module);
|
result = interpreter.run(*script_or_module);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user