This was a remnant from the AST/BC hybrid interpreter times. We've had
a VERIFY in here for weeks now that would catch anything depending on
this behavior, and nothing has hit it, so let's remove the unnecessary
code (but leave the VERIFY) :^)
Instead of trying to keep a live reference to the bytecode interpreter's
current instruction stream iterator, we now simply copy the current
iterator whenever pushing to the ExecutionContext stack.
This fixes a stack-use-after-return issue reported by ASAN.
This works by adding source start/end offset to every bytecode
instruction. In the future we can make this more efficient by keeping
a map of bytecode ranges to source ranges in the Executable instead,
but let's just get traces working first.
Co-Authored-By: Andrew Kaster <akaster@serenityos.org>
Because "this" value cannot be changed during function execution it is
safe to compute it once and then use for future access.
This optimization makes ai-astar.js run 8% faster.
This fixes an issue where returning inside a `try` block and then
calling a function inside `finally` would clobber the saved return
value from the `try` block.
Note that we didn't need to change the base of register allocation,
since it was already 1 too high.
With this fixed, https://microsoft.com/edge loads in bytecode mode. :^)
Thanks to Luke for reducing the issue!
These passes have not been shown to actually optimize any JS, and tests
have become very flaky with optimizations enabled. Until some measurable
benefit is shown, remove the optimization passes to reduce overhead of
maintaining bytecode operations and to reduce CI churn. The framework
for optimizations will live on in git history, and can be restored once
proven useful.
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.