mirror of
https://github.com/RGBCube/serenity
synced 2025-07-28 14:57:34 +00:00
LibJS: Update AST to use completions :^)
This is another major milestone on our journey towards removing global VM exception state :^) Does pretty much exactly what it says on the tin: updating ASTNode::execute() to return a Completion instead of a plain value. This will *also* allow us to eventually remove the non-standard unwinding mechanism and purely rely on the various completion types.
This commit is contained in:
parent
95acb1ce88
commit
da856d7742
11 changed files with 507 additions and 692 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2020-2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
|
@ -564,7 +564,7 @@ ThrowCompletionOr<Value> perform_eval(Value x, GlobalObject& caller_realm, Calle
|
|||
|
||||
TemporaryChange scope_change_strict(vm.running_execution_context().is_strict_mode, strict_eval);
|
||||
|
||||
Value eval_result;
|
||||
Optional<Value> eval_result;
|
||||
|
||||
if (auto* bytecode_interpreter = Bytecode::Interpreter::current()) {
|
||||
auto executable = JS::Bytecode::Generator::generate(program);
|
||||
|
@ -572,16 +572,16 @@ ThrowCompletionOr<Value> perform_eval(Value x, GlobalObject& caller_realm, Calle
|
|||
if (JS::Bytecode::g_dump_bytecode)
|
||||
executable.dump();
|
||||
eval_result = TRY(bytecode_interpreter->run(executable));
|
||||
// Turn potentially empty JS::Value from the bytecode interpreter into an empty Optional
|
||||
if (eval_result.has_value() && eval_result->is_empty())
|
||||
eval_result = {};
|
||||
} else {
|
||||
auto& ast_interpreter = vm.interpreter();
|
||||
// FIXME: We need to use evaluate_statements() here because Program::execute() calls global_declaration_instantiation() when it shouldn't
|
||||
eval_result = program->evaluate_statements(ast_interpreter, caller_realm);
|
||||
eval_result = TRY(program->evaluate_statements(ast_interpreter, caller_realm));
|
||||
}
|
||||
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
else
|
||||
return eval_result.value_or(js_undefined());
|
||||
return eval_result.value_or(js_undefined());
|
||||
}
|
||||
|
||||
// 19.2.1.3 EvalDeclarationInstantiation ( body, varEnv, lexEnv, privateEnv, strict ), https://tc39.es/ecma262/#sec-evaldeclarationinstantiation
|
||||
|
|
|
@ -50,9 +50,7 @@ ThrowCompletionOr<Object*> AsyncFunctionConstructor::construct(FunctionObject& n
|
|||
}
|
||||
|
||||
VM::InterpreterExecutionScope scope(*interpreter);
|
||||
auto result = function->execute(*interpreter, global_object());
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
auto result = TRY(function->execute(*interpreter, global_object())).release_value();
|
||||
VERIFY(result.is_object() && is<ECMAScriptFunctionObject>(result.as_object()));
|
||||
return &result.as_object();
|
||||
}
|
||||
|
|
|
@ -54,9 +54,7 @@ ThrowCompletionOr<Object*> AsyncGeneratorFunctionConstructor::construct(Function
|
|||
}
|
||||
|
||||
VM::InterpreterExecutionScope scope(*interpreter);
|
||||
auto result = function->execute(*interpreter, global_object());
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
auto result = TRY(function->execute(*interpreter, global_object())).release_value();
|
||||
VERIFY(result.is_object() && is<ECMAScriptFunctionObject>(result.as_object()));
|
||||
return &result.as_object();
|
||||
}
|
||||
|
|
|
@ -431,9 +431,7 @@ ThrowCompletionOr<void> ECMAScriptFunctionObject::function_declaration_instantia
|
|||
} else if (parameter.default_value) {
|
||||
// FIXME: Support default arguments in the bytecode world!
|
||||
if (interpreter)
|
||||
argument_value = parameter.default_value->execute(*interpreter, global_object());
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
argument_value = TRY(parameter.default_value->execute(*interpreter, global_object())).release_value();
|
||||
} else {
|
||||
argument_value = js_undefined();
|
||||
}
|
||||
|
@ -715,27 +713,26 @@ void ECMAScriptFunctionObject::async_block_start(PromiseCapability const& promis
|
|||
// c. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
|
||||
vm.pop_execution_context();
|
||||
|
||||
// NOTE: Running the AST node should eventually return a completion.
|
||||
// Until it does, we assume "return" and include the undefined fallback from the call site.
|
||||
// NOTE: Eventually we'll distinguish between normal and return completion.
|
||||
// For now, we assume "return" and include the undefined fallback from the call site.
|
||||
// d. If result.[[Type]] is normal, then
|
||||
if (false) {
|
||||
// i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « undefined »).
|
||||
MUST(call(global_object, promise_capability.resolve, js_undefined(), js_undefined()));
|
||||
}
|
||||
// e. Else if result.[[Type]] is return, then
|
||||
else if (!vm.exception()) {
|
||||
else if (result.type() == Completion::Type::Return || result.type() == Completion::Type::Normal) {
|
||||
// i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »).
|
||||
MUST(call(global_object, promise_capability.resolve, js_undefined(), result.value_or(js_undefined())));
|
||||
MUST(call(global_object, promise_capability.resolve, js_undefined(), result.value().value_or(js_undefined())));
|
||||
}
|
||||
// f. Else,
|
||||
else {
|
||||
// i. Assert: result.[[Type]] is throw.
|
||||
|
||||
// ii. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »).
|
||||
auto reason = vm.exception()->value();
|
||||
vm.clear_exception();
|
||||
vm.stop_unwind();
|
||||
MUST(call(global_object, promise_capability.reject, js_undefined(), reason));
|
||||
MUST(call(global_object, promise_capability.reject, js_undefined(), *result.value()));
|
||||
}
|
||||
// g. Return.
|
||||
return js_undefined();
|
||||
|
@ -823,11 +820,9 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
|
|||
if (m_kind == FunctionKind::Regular) {
|
||||
TRY(function_declaration_instantiation(ast_interpreter));
|
||||
|
||||
auto result = m_ecmascript_code->execute(*ast_interpreter, global_object());
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
// NOTE: Running the AST node should eventually return a completion.
|
||||
// Until it does, we assume "return" and include the undefined fallback from the call site.
|
||||
auto result = TRY(m_ecmascript_code->execute(*ast_interpreter, global_object()));
|
||||
// NOTE: Once 'return' completions are being used, we can just return the completion from execute() directly.
|
||||
// For now, we assume "return" and include the undefined fallback from the call site.
|
||||
return { Completion::Type::Return, result.value_or(js_undefined()), {} };
|
||||
} else if (m_kind == FunctionKind::Async) {
|
||||
// 1. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
|
|
|
@ -87,9 +87,7 @@ ThrowCompletionOr<Object*> FunctionConstructor::construct(FunctionObject& new_ta
|
|||
}
|
||||
|
||||
VM::InterpreterExecutionScope scope(*interpreter);
|
||||
auto result = function->execute(*interpreter, global_object());
|
||||
if (auto* exception = vm.exception())
|
||||
return throw_completion(exception->value());
|
||||
auto result = TRY(function->execute(*interpreter, global_object())).release_value();
|
||||
VERIFY(result.is_object() && is<ECMAScriptFunctionObject>(result.as_object()));
|
||||
return &result.as_object();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -117,13 +117,7 @@ ThrowCompletionOr<Value> perform_shadow_realm_eval(GlobalObject& global_object,
|
|||
// TODO: Optionally use bytecode interpreter?
|
||||
// FIXME: We need to use evaluate_statements() here because Program::execute() calls global_declaration_instantiation() when it shouldn't
|
||||
// a. Set result to the result of evaluating body.
|
||||
auto result_value = program->evaluate_statements(vm.interpreter(), eval_realm.global_object());
|
||||
if (auto* exception = vm.exception())
|
||||
result = throw_completion(exception->value());
|
||||
else if (!result_value.is_empty())
|
||||
result = normal_completion(result_value);
|
||||
else
|
||||
result = Completion {}; // Normal completion with no value
|
||||
result = program->evaluate_statements(vm.interpreter(), eval_realm.global_object());
|
||||
}
|
||||
|
||||
// 21. If result.[[Type]] is normal and result.[[Value]] is empty, then
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2020-2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2021, David Tuin <davidot@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
|
@ -160,10 +160,7 @@ ThrowCompletionOr<Value> VM::named_evaluation_if_anonymous_function(GlobalObject
|
|||
}
|
||||
}
|
||||
|
||||
auto value = expression.execute(interpreter(), global_object);
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
return value;
|
||||
return TRY(expression.execute(interpreter(), global_object)).release_value();
|
||||
}
|
||||
|
||||
// 13.15.5.2 Runtime Semantics: DestructuringAssignmentEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-destructuringassignmentevaluation
|
||||
|
@ -244,9 +241,6 @@ ThrowCompletionOr<void> VM::property_binding_initialization(BindingPattern const
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
|
||||
auto* rest_object = Object::create(global_object, global_object.object_prototype());
|
||||
VERIFY(rest_object);
|
||||
|
||||
|
@ -257,25 +251,15 @@ ThrowCompletionOr<void> VM::property_binding_initialization(BindingPattern const
|
|||
return assignment_target.initialize_referenced_binding(global_object, rest_object);
|
||||
}
|
||||
|
||||
PropertyKey name;
|
||||
|
||||
property.name.visit(
|
||||
[&](Empty) { VERIFY_NOT_REACHED(); },
|
||||
[&](NonnullRefPtr<Identifier> const& identifier) {
|
||||
name = identifier->string();
|
||||
auto name = TRY(property.name.visit(
|
||||
[&](Empty) -> ThrowCompletionOr<PropertyKey> { VERIFY_NOT_REACHED(); },
|
||||
[&](NonnullRefPtr<Identifier> const& identifier) -> ThrowCompletionOr<PropertyKey> {
|
||||
return identifier->string();
|
||||
},
|
||||
[&](NonnullRefPtr<Expression> const& expression) {
|
||||
auto result = expression->execute(interpreter(), global_object);
|
||||
if (exception())
|
||||
return;
|
||||
auto name_or_error = result.to_property_key(global_object);
|
||||
if (name_or_error.is_error())
|
||||
return;
|
||||
name = name_or_error.release_value();
|
||||
});
|
||||
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
[&](NonnullRefPtr<Expression> const& expression) -> ThrowCompletionOr<PropertyKey> {
|
||||
auto result = TRY(expression->execute(interpreter(), global_object)).release_value();
|
||||
return result.to_property_key(global_object);
|
||||
}));
|
||||
|
||||
seen_names.set(name);
|
||||
|
||||
|
@ -306,18 +290,12 @@ ThrowCompletionOr<void> VM::property_binding_initialization(BindingPattern const
|
|||
return TRY(member_expression->to_reference(interpreter(), global_object));
|
||||
}));
|
||||
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
|
||||
auto value_to_assign = TRY(object->get(name));
|
||||
if (property.initializer && value_to_assign.is_undefined()) {
|
||||
if (auto* identifier_ptr = property.alias.get_pointer<NonnullRefPtr<Identifier>>())
|
||||
value_to_assign = TRY(named_evaluation_if_anonymous_function(global_object, *property.initializer, (*identifier_ptr)->string()));
|
||||
else
|
||||
value_to_assign = property.initializer->execute(interpreter(), global_object);
|
||||
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
value_to_assign = TRY(property.initializer->execute(interpreter(), global_object)).release_value();
|
||||
}
|
||||
|
||||
if (auto* binding_ptr = property.alias.get_pointer<NonnullRefPtr<BindingPattern>>()) {
|
||||
|
@ -405,10 +383,7 @@ ThrowCompletionOr<void> VM::iterator_binding_initialization(BindingPattern const
|
|||
if (auto* identifier_ptr = entry.alias.get_pointer<NonnullRefPtr<Identifier>>())
|
||||
value = TRY(named_evaluation_if_anonymous_function(global_object, *entry.initializer, (*identifier_ptr)->string()));
|
||||
else
|
||||
value = entry.initializer->execute(interpreter(), global_object);
|
||||
|
||||
if (auto* thrown_exception = exception())
|
||||
return JS::throw_completion(thrown_exception->value());
|
||||
value = TRY(entry.initializer->execute(interpreter(), global_object)).release_value();
|
||||
}
|
||||
|
||||
if (auto* binding_ptr = entry.alias.get_pointer<NonnullRefPtr<BindingPattern>>()) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue