If an exception was thrown while evaluating the argument of an `await`
expression, we should jump to the continuation block instead of eagerly
rejecting the caller async function.
This restores the behavior prior to the addition of the separate `Await`
instruction in d66eb4e3.
If the exception from the `try` block has already been caught by
`catch`, we need to clear the saved exception before entering `finally`
so that ContinuePendingUnwind will not re-throw it.
9 new passes on test262 :^)
The instructions GetById and GetByIdWithThis now remember the last-seen
Shape, and if we see the same object again, we reuse the property offset
from last time without doing a new lookup.
This allows us to use Object::get_direct(), bypassing the entire lookup
machinery and saving lots of time.
~23% speed-up on Kraken/ai-astar.js :^)
The var environments will unwind as needed with the ExecutionContext
and there's no need to include it in the unwind info.
We still need to do this for lexical environments though, since they
can have short local lifetimes inside a function.
Since the relationship between VM and Bytecode::Interpreter is now
clear, we can have VM ask the Interpreter for roots in the GC marking
pass. This avoids having to register and unregister handles and
MarkedVectors over and over.
Since GeneratorObject can also own a RegisterWindow, we share the code
in a RegisterWindow::visit_edges() helper.
~4% speed-up on Kraken/stanford-crypto-ccm.js :^)
While this would be useful in the future for implementing a multi-tiered
optimization strategy, currently a binary on/off is enough for us. This
removes the confusingly on-by-default `OptimizationLevel::None` option
which made the optimization pipeline a no-op even if
`Bytecode::Interpreter::set_optimizations_enabled` had been called.
Fixes#15982
The JS::VM now owns the one Bytecode::Interpreter. We no longer have
multiple bytecode interpreters, and there is no concept of a "current"
bytecode interpreter.
If you ask for VM::bytecode_interpreter_if_exists(), it will return null
if we're not running the program in "bytecode enabled" mode.
If you ask for VM::bytecode_interpreter(), it will return a bytecode
interpreter in all modes. This is used for situations where even the AST
interpreter switches to bytecode mode (generators, etc.)
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.
Unwind contexts now remember the lexical and variable environments in
effect when they were created. If an exception is caught, we revert
to those environments in the running execution context.
We use generators in bytecode to approximate async functions, but the
code generated by AwaitExpressions did not have the value processing
paths that Yield requires, eg the `generator.throw()` path, which is
used by AsyncFunctionDriverWrapper to signal Promise rejections.
This uses a newly added instruction `ScheduleJump`
This instruction tells the finally proceeding it, that instead of
jumping to it's next block it should jump to the designated block.
DeprecatedFlyString relies heavily on DeprecatedString's StringImpl, so
let's rename it to A) match the name of DeprecatedString, B) write a new
FlyString class that is tied to String.
This is still not perfect, as we now actually crash in the
`try-finally-continue` tests, while we now succeed all
`try-catch-finally-*` tests.
Note that we do not yet go through the finally block when exiting the
unwind context through a break or continue.
We are already doing this in a good manner via the generated code,
doing so in the execution loop as well will cause us to pop contexts
multiple times, which is not very good.
We have a new, improved string type coming up in AK (OOM aware, no null
state), and while it's going to use UTF-8, the name UTF8String is a
mouthful - so let's free up the String name by renaming the existing
class.
Making the old one have an annoying name will hopefully also help with
quick adoption :^)
This pass tries to eliminate repeated lookups of variables by name, by
remembering where these where last loaded to.
For now the lookup cache needs to be fully cleared with each call or
property access, because we do not have a way to check if these have any
side effects on the currently visible scopes.
Note that property accesses can cause getters/setters to be called, so
these are treated as calls in all cases.
Unwind contexts need to be preserved as we exit and re-enter a
generator.
For example, this would previously crash when returning from the try
statement after yielding as we lost the unwind context when yielding,
but still have a LeaveUnwindContext instruction from running
`perform_needed_unwinds` when generating the return statement.
```js
function* a() {
try {
return (yield 1);
} catch {}
}
iter = a();
iter.next();
iter.next();
```
The optimization passes are not stable, which makes test262 flaky.
Address this by introducing a new OptimizationLevel::None and making it
the default.
This removes all the flakiness from test262 in my testing.
We can enable optimizations by default again once they have been made
stable. :^)
The basic idea is that a global object cannot just come out of nowhere,
it must be associated to a realm - so get it from there, if needed.
This is to enforce the changes from all the previous commits by not
handing out global objects unless you actually have an initialized
realm (either stored somewhere, or the VM's current realm).
An executable is generated for the top-level script and for each
function. Strict mode can only be changed with the first statement of
the top-level script and each function, which corresponds directly to
Executable.
It makes no sense to require passing a global object and doing a stack
space check in some cases where running out of stack is highly unlikely,
we can't recover from errors, and currently ignore the result anyway.
This is most commonly in constructors and when setting things up, rather
than regular function calls.
For example, a try/catch block with no finally. The try block and catch
block do not need to unwind to a finally block, so the unwind context
is no longer needed when we jump to the catch block.
If we threw an exception in a catch block of a try/catch, there will be
no handler or finalizer and the unit would continue on as if nothing
happened.
This would subsequently crash with the `m_saved_exception.is_null()`
assertion failure when we next call a non-native function.
Now we emit CreateVariable and SetVariable with the appropriate
initialization/environment modes, much closer to the spec.
This makes a whole lot of things like let/const variables, function
and variable hoisting and some other things work :^)
This commit removes all exception related code:
Remove VM::exception(), VM::throw_exception() etc. Any leftover
throw_exception calls are moved to throw_completion.
The one method left is clear_exception() which is now a no-op. Most of
these calls are just to clear whatever exception might have been thrown
when handling a Completion. So to have a cleaner commit this will be
removed in a next commit.
It also removes the actual Exception and TemporaryClearException classes
since these are no longer used.
In any spot where the exception was actually used an attempt was made to
preserve that behavior. However since it is no longer tracked by the VM
we cannot access exceptions which were thrown in previous calls.
There are two such cases which might have different behavior:
- In Web::DOM::Document::interpreter() the on_call_stack_emptied hook
used to print any uncaught exception but this is now no longer
possible as the VM does not store uncaught exceptions.
- In js the code used to be interruptable by throwing an exception on
the VM. This is no longer possible but was already somewhat fragile
before as you could happen to throw an exception just before a VERIFY.
Because we now push an execution context when creating the "normal"
interpreter without valid environments we have to check for that case
as well when running the bytecode interpreter.
And use it to _correctly_ implement state saving for generators.
Prior to this, we were capturing the caller frame, which is completely
irrelevant to the generator frame.
To support situations like this:
function foo() { throw 1; }
try {
foo();
} catch (e) {
}
Each unwind context now keeps track of its origin executable.
When an exception is thrown, we return from run() immediately if the
nearest unwind context isn't in the current executable.
This causes a natural unwind to the point where we find the
catch/finally block(s) to jump into.
We were missing some "break" statements, causing us to actually finish
executing everything within "try" blocks before actually jumping to the
"catch" and/or "finally" blocks.