1
Fork 0
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:
Linus Groh 2022-01-02 21:37:50 +01:00
parent 95acb1ce88
commit da856d7742
11 changed files with 507 additions and 692 deletions

View file

@ -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

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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%).

View file

@ -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();
}

View file

@ -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

View file

@ -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>>()) {