mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 06:47:35 +00:00
LibJS: Replace the custom unwind mechanism with completions :^)
This includes: - Parsing proper LabelledStatements with try_parse_labelled_statement() - Removing LabelableStatement - Implementing the LoopEvaluation semantics via loop_evaluation() in each IterationStatement subclass; and IterationStatement evaluation via {For,ForIn,ForOf,ForAwaitOf,While,DoWhile}Statement::execute() - Updating ReturnStatement, BreakStatement and ContinueStatement to return the appropriate completion types - Basically reimplementing TryStatement and SwitchStatement according to the spec, using completions - Honoring result completion types in AsyncBlockStart and OrdinaryCallEvaluateBody - Removing any uses of the VM unwind mechanism - most importantly, VM::throw_exception() now exclusively sets an exception and no longer triggers any unwinding mechanism. However, we already did a good job updating all of LibWeb and userland applications to not use it, and the few remaining uses elsewhere don't rely on unwinding AFAICT.
This commit is contained in:
parent
eed764e1dd
commit
9d0d3affd4
16 changed files with 512 additions and 279 deletions
|
@ -39,7 +39,6 @@ ThrowCompletionOr<Value> AsyncFunctionDriverWrapper::react_to_async_task_complet
|
|||
if (generator_result.is_throw_completion()) {
|
||||
VERIFY(generator_result.throw_completion().type() == Completion::Type::Throw);
|
||||
vm.clear_exception();
|
||||
vm.stop_unwind();
|
||||
auto promise = Promise::create(global_object);
|
||||
promise->reject(*generator_result.throw_completion().value());
|
||||
return promise;
|
||||
|
|
|
@ -171,8 +171,6 @@ ThrowCompletionOr<Value> ECMAScriptFunctionObject::internal_call(Value this_argu
|
|||
|
||||
// 9. ReturnIfAbrupt(result).
|
||||
if (result.is_abrupt()) {
|
||||
// NOTE: I'm not sure if EvaluateBody can return a completion other than Normal, Return, or Throw.
|
||||
// We're far from using completions in the AST anyway; in the meantime assume Throw.
|
||||
VERIFY(result.is_error());
|
||||
return result;
|
||||
}
|
||||
|
@ -266,9 +264,7 @@ ThrowCompletionOr<Object*> ECMAScriptFunctionObject::internal_construct(MarkedVa
|
|||
return vm.throw_completion<TypeError>(global_object, ErrorType::DerivedConstructorReturningInvalidValue);
|
||||
}
|
||||
// 11. Else, ReturnIfAbrupt(result).
|
||||
else {
|
||||
// NOTE: I'm not sure if EvaluateBody can return a completion other than Normal, Return, or Throw.
|
||||
// We're far from using completions in the AST anyway; in the meantime assume Throw.
|
||||
else if (result.is_abrupt()) {
|
||||
VERIFY(result.is_error());
|
||||
return result;
|
||||
}
|
||||
|
@ -713,25 +709,23 @@ 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: 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) {
|
||||
if (result.type() == Completion::Type::Normal) {
|
||||
// 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 (result.type() == Completion::Type::Return || result.type() == Completion::Type::Normal) {
|
||||
else if (result.type() == Completion::Type::Return) {
|
||||
// i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »).
|
||||
MUST(call(global_object, promise_capability.resolve, js_undefined(), result.value().value_or(js_undefined())));
|
||||
MUST(call(global_object, promise_capability.resolve, js_undefined(), *result.value()));
|
||||
}
|
||||
// f. Else,
|
||||
else {
|
||||
// i. Assert: result.[[Type]] is throw.
|
||||
VERIFY(result.type() == Completion::Type::Throw);
|
||||
|
||||
// ii. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »).
|
||||
vm.clear_exception();
|
||||
vm.stop_unwind();
|
||||
MUST(call(global_object, promise_capability.reject, js_undefined(), *result.value()));
|
||||
}
|
||||
// g. Return.
|
||||
|
@ -756,6 +750,7 @@ void ECMAScriptFunctionObject::async_block_start(PromiseCapability const& promis
|
|||
}
|
||||
|
||||
// 10.2.1.4 OrdinaryCallEvaluateBody ( F, argumentsList ), https://tc39.es/ecma262/#sec-ordinarycallevaluatebody
|
||||
// 15.8.4 Runtime Semantics: EvaluateAsyncFunctionBody, https://tc39.es/ecma262/#sec-runtime-semantics-evaluatefunctionbody
|
||||
Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
@ -817,14 +812,16 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
|
|||
|
||||
VM::InterpreterExecutionScope scope(*ast_interpreter);
|
||||
|
||||
// FunctionBody : FunctionStatementList
|
||||
if (m_kind == FunctionKind::Regular) {
|
||||
// 1. Perform ? FunctionDeclarationInstantiation(functionObject, argumentsList).
|
||||
TRY(function_declaration_instantiation(ast_interpreter));
|
||||
|
||||
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) {
|
||||
// 2. Return the result of evaluating FunctionStatementList.
|
||||
return m_ecmascript_code->execute(*ast_interpreter, global_object());
|
||||
}
|
||||
// AsyncFunctionBody : FunctionBody
|
||||
else if (m_kind == FunctionKind::Async) {
|
||||
// 1. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
auto promise_capability = MUST(new_promise_capability(global_object(), global_object().promise_constructor()));
|
||||
|
||||
|
|
|
@ -120,8 +120,6 @@ static Completion iterator_close_(Object& iterator, Completion completion, Itera
|
|||
if (!return_method)
|
||||
return completion;
|
||||
|
||||
vm.stop_unwind();
|
||||
|
||||
// c. Set innerResult to Call(return, iterator).
|
||||
auto result_or_error = vm.call(*return_method, &iterator);
|
||||
if (result_or_error.is_error()) {
|
||||
|
|
|
@ -115,7 +115,6 @@ Promise::ResolvingFunctions Promise::create_resolving_functions()
|
|||
// a. Return RejectPromise(promise, then.[[Value]]).
|
||||
dbgln_if(PROMISE_DEBUG, "[Promise @ {} / PromiseResolvingFunction]: Exception while getting 'then' property, rejecting with error", &promise);
|
||||
vm.clear_exception();
|
||||
vm.stop_unwind();
|
||||
return promise.reject(*then.throw_completion().value());
|
||||
}
|
||||
|
||||
|
|
|
@ -329,7 +329,6 @@ ThrowCompletionOr<Object*> PromiseConstructor::construct(FunctionObject& new_tar
|
|||
// 10. If completion is an abrupt completion, then
|
||||
if (auto* exception = vm.exception()) {
|
||||
vm.clear_exception();
|
||||
vm.stop_unwind();
|
||||
|
||||
// a. Perform ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »).
|
||||
TRY(vm.call(reject_function, js_undefined(), exception->value()));
|
||||
|
|
|
@ -84,7 +84,6 @@ ThrowCompletionOr<Value> PromiseReactionJob::call()
|
|||
// h. If handlerResult is an abrupt completion, then
|
||||
if (handler_result.is_abrupt()) {
|
||||
vm.clear_exception();
|
||||
vm.stop_unwind();
|
||||
|
||||
// i. Let status be Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »).
|
||||
auto* reject_function = promise_capability.value().reject;
|
||||
|
@ -139,7 +138,6 @@ ThrowCompletionOr<Value> PromiseResolveThenableJob::call()
|
|||
// c. If thenCallResult is an abrupt completion, then
|
||||
if (then_call_result.is_error()) {
|
||||
vm.clear_exception();
|
||||
vm.stop_unwind();
|
||||
|
||||
// i. Let status be Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »).
|
||||
dbgln_if(PROMISE_DEBUG, "[PromiseResolveThenableJob @ {}]: then_call_result is an abrupt completion, calling reject function with value {}", this, *then_call_result.throw_completion().value());
|
||||
|
|
|
@ -26,7 +26,6 @@ struct PromiseCapability {
|
|||
/* 1. If value is an abrupt completion, then */ \
|
||||
if (_temporary_try_or_reject_result.is_error()) { \
|
||||
vm.clear_exception(); \
|
||||
vm.stop_unwind(); \
|
||||
\
|
||||
/* a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »). */ \
|
||||
TRY(vm.call(*capability.reject, js_undefined(), *_temporary_try_or_reject_result.release_error().value())); \
|
||||
|
|
|
@ -468,7 +468,6 @@ ThrowCompletionOr<void> VM::initialize_instance_elements(Object& object, ECMAScr
|
|||
void VM::throw_exception(Exception& exception)
|
||||
{
|
||||
set_exception(exception);
|
||||
unwind(ScopeType::Try);
|
||||
}
|
||||
|
||||
// 9.4.4 ResolveThisBinding ( ), https://tc39.es/ecma262/#sec-resolvethisbinding
|
||||
|
@ -547,7 +546,6 @@ void VM::run_queued_promise_jobs()
|
|||
// exceptions when running Promise jobs. See the commit where these two lines were initially
|
||||
// added for a much more detailed explanation.
|
||||
clear_exception();
|
||||
stop_unwind();
|
||||
|
||||
if (pushed_execution_context)
|
||||
pop_execution_context();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue