2021-06-03 10:46:30 +02:00
/*
* Copyright ( c ) 2021 , Andreas Kling < kling @ serenityos . org >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2021-06-07 15:17:37 +02:00
# include <AK/Debug.h>
2021-06-09 10:02:01 +02:00
# include <AK/TemporaryChange.h>
2023-06-15 12:36:57 +02:00
# include <LibJS/AST.h>
2021-06-09 06:49:58 +04:30
# include <LibJS/Bytecode/BasicBlock.h>
2023-06-17 13:16:35 +02:00
# include <LibJS/Bytecode/Generator.h>
2021-06-03 10:46:30 +02:00
# include <LibJS/Bytecode/Instruction.h>
# include <LibJS/Bytecode/Interpreter.h>
2021-06-09 09:19:34 +02:00
# include <LibJS/Bytecode/Op.h>
2021-07-01 12:24:46 +02:00
# include <LibJS/Runtime/GlobalEnvironment.h>
2021-06-03 18:26:13 +02:00
# include <LibJS/Runtime/GlobalObject.h>
2021-09-11 20:27:36 +01:00
# include <LibJS/Runtime/Realm.h>
2021-06-03 10:46:30 +02:00
namespace JS : : Bytecode {
2021-10-24 13:34:46 +02:00
bool g_dump_bytecode = false ;
2021-06-05 15:53:36 +02:00
2023-06-22 15:59:18 +02:00
Interpreter : : Interpreter ( VM & vm )
: m_vm ( vm )
2021-06-05 15:53:36 +02:00
{
2021-06-03 10:46:30 +02:00
}
Interpreter : : ~ Interpreter ( )
{
}
2023-07-02 12:53:10 +02:00
void Interpreter : : visit_edges ( Cell : : Visitor & visitor )
{
2023-07-20 10:46:42 +02:00
for ( auto & frame : m_call_frames ) {
frame . visit ( [ & ] ( auto & value ) { value - > visit_edges ( visitor ) ; } ) ;
2023-07-02 12:53:10 +02:00
}
}
2023-06-15 12:36:57 +02:00
// 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 ( g_dump_bytecode )
executable - > dump ( ) ;
// a. Set result to the result of evaluating script.
2023-09-21 09:26:32 +02:00
auto result_or_error = run_and_return_frame ( * executable , nullptr ) ;
2023-06-15 12:36:57 +02:00
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 ( ) ;
}
2023-09-26 16:41:42 +02:00
void Interpreter : : run_bytecode ( )
2021-06-03 10:46:30 +02:00
{
2021-06-09 06:49:58 +04:30
for ( ; ; ) {
2023-09-26 16:45:55 +02:00
start :
2023-09-01 16:53:55 +02:00
auto pc = InstructionStreamIterator { m_current_block - > instruction_stream ( ) , m_current_executable } ;
TemporaryChange temp_change { m_pc , Optional < InstructionStreamIterator & > ( pc ) } ;
2022-10-30 11:58:46 +01:00
2021-06-09 06:49:58 +04:30
bool will_return = false ;
2022-12-06 20:45:38 +01:00
bool will_yield = false ;
2023-09-26 16:41:42 +02:00
2021-06-09 06:49:58 +04:30
while ( ! pc . at_end ( ) ) {
auto & instruction = * pc ;
2023-09-26 17:14:59 +02:00
2022-02-07 14:36:45 +01:00
auto ran_or_error = instruction . execute ( * this ) ;
2023-09-26 16:05:22 +02:00
if ( ran_or_error . is_error ( ) ) [[unlikely]] {
2023-09-26 11:57:42 +02:00
reg ( Register : : exception ( ) ) = * ran_or_error . throw_completion ( ) . value ( ) ;
2022-11-25 23:15:44 +00:00
if ( unwind_contexts ( ) . is_empty ( ) )
2023-09-26 17:14:59 +02:00
return ;
2022-11-25 23:15:44 +00:00
auto & unwind_context = unwind_contexts ( ) . last ( ) ;
2021-10-25 12:30:54 +02:00
if ( unwind_context . executable ! = m_current_executable )
2023-09-26 17:14:59 +02:00
return ;
2023-07-14 15:50:36 +02:00
if ( unwind_context . handler & & ! unwind_context . handler_called ) {
2023-05-13 18:53:14 +02:00
vm ( ) . running_execution_context ( ) . lexical_environment = unwind_context . lexical_environment ;
2022-10-30 11:58:46 +01:00
m_current_block = unwind_context . handler ;
2023-07-14 15:50:36 +02:00
unwind_context . handler_called = true ;
2022-03-14 02:20:50 +00:00
2023-09-26 11:57:42 +02:00
accumulator ( ) = reg ( Register : : exception ( ) ) ;
reg ( Register : : exception ( ) ) = { } ;
2023-09-26 16:45:55 +02:00
goto start ;
2021-10-24 23:52:39 +02:00
}
if ( unwind_context . finalizer ) {
2022-10-30 11:58:46 +01:00
m_current_block = unwind_context . finalizer ;
2023-07-14 15:50:36 +02:00
// If an exception was thrown inside the corresponding `catch` block, we need to rethrow it
// from the `finally` block. But if the exception is from the `try` block, and has already been
// handled by `catch`, we swallow it.
if ( ! unwind_context . handler_called )
2023-09-26 11:57:42 +02:00
reg ( Register : : exception ( ) ) = { } ;
2023-09-26 16:45:55 +02:00
goto start ;
2021-06-10 15:04:38 +02:00
}
2022-03-14 02:20:50 +00:00
// An unwind context with no handler or finalizer? We have nowhere to jump, and continuing on will make us crash on the next `Call` to a non-native function if there's an exception! So let's crash here instead.
// If you run into this, you probably forgot to remove the current unwind_context somewhere.
VERIFY_NOT_REACHED ( ) ;
2021-06-10 15:04:38 +02:00
}
2021-06-09 06:49:58 +04:30
if ( m_pending_jump . has_value ( ) ) {
2022-10-30 11:58:46 +01:00
m_current_block = m_pending_jump . release_value ( ) ;
2023-09-26 16:45:55 +02:00
goto start ;
2021-06-09 06:49:58 +04:30
}
2023-09-26 15:32:46 +02:00
if ( ! reg ( Register : : return_value ( ) ) . is_empty ( ) ) {
2021-06-09 06:49:58 +04:30
will_return = true ;
2022-12-06 20:45:38 +01:00
// Note: A `yield` statement will not go through a finally statement,
// hence we need to set a flag to not do so,
// but we generate a Yield Operation in the case of returns in
// generators as well, so we need to check if it will actually
// continue or is a `return` in disguise
2023-07-15 09:59:19 +02:00
will_yield = ( instruction . type ( ) = = Instruction : : Type : : Yield & & static_cast < Op : : Yield const & > ( instruction ) . continuation ( ) . has_value ( ) ) | | instruction . type ( ) = = Instruction : : Type : : Await ;
2021-06-09 06:49:58 +04:30
break ;
}
+ + pc ;
2021-06-04 12:07:38 +02:00
}
2021-06-09 06:49:58 +04:30
2022-12-06 20:45:38 +01:00
if ( ! unwind_contexts ( ) . is_empty ( ) & & ! will_yield ) {
2022-11-13 20:56:53 +01:00
auto & unwind_context = unwind_contexts ( ) . last ( ) ;
if ( unwind_context . executable = = m_current_executable & & unwind_context . finalizer ) {
2023-09-26 15:32:46 +02:00
reg ( Register : : saved_return_value ( ) ) = reg ( Register : : return_value ( ) ) ;
reg ( Register : : return_value ( ) ) = { } ;
2022-11-13 20:56:53 +01:00
m_current_block = unwind_context . finalizer ;
// the unwind_context will be pop'ed when entering the finally block
continue ;
}
}
2021-06-09 06:49:58 +04:30
2022-11-13 20:56:53 +01:00
if ( pc . at_end ( ) )
2021-06-05 15:53:36 +02:00
break ;
2021-06-09 18:19:11 +02:00
2022-11-13 20:56:53 +01:00
if ( will_return )
break ;
2021-06-04 12:07:38 +02:00
}
2023-09-26 16:41:42 +02:00
}
Interpreter : : ValueAndFrame Interpreter : : run_and_return_frame ( Executable & executable , BasicBlock const * entry_point , CallFrame * in_frame )
{
dbgln_if ( JS_BYTECODE_DEBUG , " Bytecode::Interpreter will run unit {:p} " , & executable ) ;
TemporaryChange restore_executable { m_current_executable , & executable } ;
TemporaryChange restore_saved_jump { m_scheduled_jump , static_cast < BasicBlock const * > ( nullptr ) } ;
VERIFY ( ! vm ( ) . execution_context_stack ( ) . is_empty ( ) ) ;
TemporaryChange restore_current_block { m_current_block , entry_point ? : executable . basic_blocks . first ( ) } ;
if ( in_frame )
push_call_frame ( in_frame , executable . number_of_registers ) ;
else
push_call_frame ( make < CallFrame > ( ) , executable . number_of_registers ) ;
run_bytecode ( ) ;
2021-06-03 10:46:30 +02:00
2021-06-09 09:11:20 +02:00
dbgln_if ( JS_BYTECODE_DEBUG , " Bytecode::Interpreter did run unit {:p} " , & executable ) ;
2021-06-07 15:17:37 +02:00
if constexpr ( JS_BYTECODE_DEBUG ) {
for ( size_t i = 0 ; i < registers ( ) . size ( ) ; + + i ) {
2023-02-12 21:54:02 -05:00
String value_string ;
2021-06-07 15:17:37 +02:00
if ( registers ( ) [ i ] . is_empty ( ) )
2023-08-07 11:12:38 +02:00
value_string = " (empty) " _string ;
2021-06-07 15:17:37 +02:00
else
2023-08-09 08:49:02 +02:00
value_string = registers ( ) [ i ] . to_string_without_side_effects ( ) ;
2021-06-07 15:17:37 +02:00
dbgln ( " [{:3}] {} " , i , value_string ) ;
}
2021-06-03 10:46:30 +02:00
}
2021-06-05 15:53:36 +02:00
2023-09-26 15:32:46 +02:00
auto return_value = js_undefined ( ) ;
if ( ! reg ( Register : : return_value ( ) ) . is_empty ( ) )
return_value = reg ( Register : : return_value ( ) ) ;
else if ( ! reg ( Register : : saved_return_value ( ) ) . is_empty ( ) )
return_value = reg ( Register : : saved_return_value ( ) ) ;
2023-09-26 11:57:42 +02:00
auto exception = reg ( Register : : exception ( ) ) ;
2023-07-21 17:30:07 +02:00
2023-07-20 10:46:42 +02:00
auto frame = pop_call_frame ( ) ;
2021-06-05 15:53:36 +02:00
// NOTE: The return value from a called function is put into $0 in the caller context.
2023-07-20 10:46:42 +02:00
if ( ! m_call_frames . is_empty ( ) )
call_frame ( ) . registers [ 0 ] = return_value ;
2021-06-05 15:53:36 +02:00
2021-11-11 00:44:56 +03:30
// 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.
vm ( ) . run_queued_promise_jobs ( ) ;
2021-06-12 17:32:54 +03:00
vm ( ) . finish_execution_generation ( ) ;
2023-09-26 11:57:42 +02:00
if ( ! exception . is_empty ( ) ) {
2023-07-20 10:46:42 +02:00
if ( auto * call_frame = frame . get_pointer < NonnullOwnPtr < CallFrame > > ( ) )
2023-09-26 11:57:42 +02:00
return { throw_completion ( exception ) , move ( * call_frame ) } ;
return { throw_completion ( exception ) , nullptr } ;
2022-02-07 14:36:45 +01:00
}
2021-11-11 04:11:56 +03:30
2023-07-20 10:46:42 +02:00
if ( auto * call_frame = frame . get_pointer < NonnullOwnPtr < CallFrame > > ( ) )
return { return_value , move ( * call_frame ) } ;
2022-04-15 20:20:51 +04:30
return { return_value , nullptr } ;
2021-06-03 10:46:30 +02:00
}
2021-06-10 15:04:38 +02:00
void Interpreter : : enter_unwind_context ( Optional < Label > handler_target , Optional < Label > finalizer_target )
{
2023-05-13 18:53:14 +02:00
unwind_contexts ( ) . empend (
m_current_executable ,
handler_target . has_value ( ) ? & handler_target - > block ( ) : nullptr ,
finalizer_target . has_value ( ) ? & finalizer_target - > block ( ) : nullptr ,
2023-07-02 15:05:17 +02:00
vm ( ) . running_execution_context ( ) . lexical_environment ) ;
2021-06-10 15:04:38 +02:00
}
void Interpreter : : leave_unwind_context ( )
{
2022-11-25 23:15:44 +00:00
unwind_contexts ( ) . take_last ( ) ;
2021-06-10 15:04:38 +02:00
}
2022-02-07 14:36:45 +01:00
ThrowCompletionOr < void > Interpreter : : continue_pending_unwind ( Label const & resume_label )
2021-06-10 15:04:38 +02:00
{
2023-09-26 11:57:42 +02:00
if ( auto exception = reg ( Register : : exception ( ) ) ; ! exception . is_empty ( ) )
return throw_completion ( exception ) ;
2022-02-07 14:36:45 +01:00
2023-07-21 17:30:07 +02:00
if ( ! saved_return_value ( ) . is_empty ( ) ) {
do_return ( saved_return_value ( ) ) ;
2022-11-13 20:56:53 +01:00
return { } ;
}
2022-11-25 16:15:34 +01:00
if ( m_scheduled_jump ) {
// FIXME: If we `break` or `continue` in the finally, we need to clear
// this field
jump ( Label { * m_scheduled_jump } ) ;
m_scheduled_jump = nullptr ;
} else {
jump ( resume_label ) ;
}
2022-02-07 14:36:45 +01:00
return { } ;
2021-06-10 15:04:38 +02:00
}
2021-06-13 20:40:20 +04:30
2023-06-22 15:59:18 +02:00
ThrowCompletionOr < NonnullOwnPtr < Bytecode : : Executable > > compile ( VM & vm , ASTNode const & node , FunctionKind kind , DeprecatedFlyString const & name )
{
auto executable_result = Bytecode : : Generator : : generate ( node , kind ) ;
if ( executable_result . is_error ( ) )
return vm . throw_completion < InternalError > ( ErrorType : : NotImplemented , TRY_OR_THROW_OOM ( vm , executable_result . error ( ) . to_string ( ) ) ) ;
auto bytecode_executable = executable_result . release_value ( ) ;
bytecode_executable - > name = name ;
2023-06-26 20:10:11 +02:00
2023-06-22 15:59:18 +02:00
if ( Bytecode : : g_dump_bytecode )
bytecode_executable - > dump ( ) ;
return bytecode_executable ;
}
Realm & Interpreter : : realm ( )
{
return * m_vm . current_realm ( ) ;
}
2023-07-20 10:46:42 +02:00
void Interpreter : : push_call_frame ( Variant < NonnullOwnPtr < CallFrame > , CallFrame * > frame , size_t register_count )
2023-07-01 13:56:40 +02:00
{
2023-07-20 10:46:42 +02:00
m_call_frames . append ( move ( frame ) ) ;
this - > call_frame ( ) . registers . resize ( register_count ) ;
m_current_call_frame = this - > call_frame ( ) . registers ;
2023-09-26 15:32:46 +02:00
reg ( Register : : return_value ( ) ) = { } ;
2023-07-01 13:56:40 +02:00
}
2023-07-20 10:46:42 +02:00
Variant < NonnullOwnPtr < CallFrame > , CallFrame * > Interpreter : : pop_call_frame ( )
2023-07-01 13:56:40 +02:00
{
2023-07-20 10:46:42 +02:00
auto frame = m_call_frames . take_last ( ) ;
m_current_call_frame = m_call_frames . is_empty ( ) ? Span < Value > { } : this - > call_frame ( ) . registers ;
return frame ;
2023-07-01 13:56:40 +02:00
}
2021-06-03 10:46:30 +02:00
}