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.
In object binding, we would attempt to get NonnullRefPtr<Identifier>
from alias on the alias.has<Empty>() code path. In this case, we need
to get it from name instead.
The update block can generate bytecode that refers to the lexical
environment, so we have to end the scope after it has been generated.
Previously the Jump to the update block would terminate the block,
causing us to leave the lexical environment just before jumping to the
update block.
After we terminate a block (e.g. break, continue), we cannot generate
anymore bytecode for the block. This caused us to crash with this
example code:
```
a = 0;
switch (a) {
case 0:
break;
console.log("hello world");
}
```
Anything after a block terminating instruction is considered
unreachable code, so we can safely skip any statements after it.
Listing all the registers will lead to the inability to allocate enough
space in one basic block (as there can be an arbitrary number of
registers used), instead switch to specifying the range of registers
used and save a lot of space in the process.
This follows how the regular AST interpreter creates arrays, as using
Array::create_from uses create_data_property_or_throw, which will crash
when it encounters an empty value. We require empty values to represent
array holes.
This will leave any lexical/variable environments on the way to the
closest unwind context boundary.
This will not leave the closest unwind context, as we still need the
unwind context to perform the Throw instruction correctly.
When we reach a block terminating instruction (e.g. Break, Throw),
we cannot generate anymore instructions after it. This would not allow
us to leave any lexical/variable environments.
This uses the mechanism introduced in ba9c49 to unwind environments
when we encounter these instructions.
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.
Previously we would only end these scopes if the block was not
terminated. If the block was generated, we would not end the scope
and would generate other bytecode with these scopes still open.
These functions do not generate any code, so they can be used even if
the current block is terminated. The enter and end scope functions are
only used to track where to unwind to when break/continue are used.
This was not handling the nullary call case correctly, remove the whole
nullary check as there's nothing particularly expensive in the catch-all
case anyway.
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 :^)
Instead of crashing on the spot, return a descriptive error that will
eventually continue its days as a javascript "InternalError" exception.
This should make random crashes with BC less likely.
When performing GetValue on a primitive type we do not need to perform
the ToObject conversion as it will resolve to a property on the
prototype object.
To avoid this we skip the initial ToObject conversion on the base value
as it only serves to get the primitive's boxed prototype. We further
specialize on PrimitiveString in order to get efficient behaviour
behaviour for the direct properties.
Depending on the tests anywhere from 20 to 60%, with significant loop
overhead.
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.
This removes a number of vm.exception() checks which are now caught
directly by TRY. Make use of these checks in
{Global, Eval}DeclarationInstantiation and while we're here add spec
comments.
Using an Optional was extremely wasteful for function objects that don't
even have a bytecode executable.
This allows ECMAScriptFunctionObject to fit in a smaller size class.
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.
Instead of using plain objects as Iterator records, causes confusion
about the object itself actually being its [[Iterator]] slot, and
requires non-standard type conversion shenanigans fpr the [[NextValue]]
and [[Done]] internal slots, implement a proper Iterator record struct
and use it throughout.
Also annotate the remaining Iterator AOs with spec comments while we're
here.
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.
In the end this is a nicer API than having separate has_{value,target}()
and having to check those first, and then making another Optional from
the unwrapped value:
completion.has_value() ? completion.value() : Optional<Value> {}
// ^^^^^^^^^^^^^^^^^^
// Implicit creation of non-empty Optional<Value>
This way we need to unwrap the optional ourselves, but can easily pass
it to something else as well.
This is in anticipation of the AST using completions :^)
The spec has a note stating that resolve binding will always return a
reference whose [[ReferencedName]] field is name. However this is not
correct as the underlying method GetIdentifierReference may throw on
env.HasBinding(name) thus it can throw. However, there are some
scenarios where it cannot throw because the reference is known to exist
in that case we use MUST with a comment.
This isn't a complete conversion to ErrorOr<void>, but a good chunk.
The end goal here is to propagate buffer allocation failures to the
caller, and allow the use of TRY() with formatting functions.