Each of these strings would previously rely on StringView's char const*
constructor overload, which would call __builtin_strlen on the string.
Since we now have operator ""sv, we can replace these with much simpler
versions. This opens the door to being able to remove
StringView(char const*).
No functional changes.
If the for loop's body is not block terminated, we will generate a Jump
to the end block which will block terminate the body. Then, we ended
the lexical variable scope if needed. However, since the body is now
block terminated, the "LeaveLexicalEnvironment" instruction that is
generated by end_variable_scope is now dropped on the floor.
This fixes this by moving it to the beginning of the end block.
Previously we only did this if the body block was not terminated.
If it was, all future codegen would happen in this block terminated
body block until another switch occurred, dropping all generated
instructions in this time on the floor.
This allows you to recurse into a named function that is stored in a
variable. For example, this would previously print "wrong" instead of
"right":
```js
function g() { console.log("wrong") }
f = function g(i) { if (i !== 1) g(1); else console.log("right"); }
f()
```
Previously it would pass in `is_arrow_function` as
`contains_direct_call_to_eval`, which broke strict mode propagation in
arrow functions. This makes test-js work without falling apart because
`this` is mysteriously undefined because of the use of arrow functions
inside classes, which are strict mode by default.
This is done by keeping track of all the labels that apply to a given
break/continue scope alongside their bytecode target. When a
break/continue with a label is generated, we scan from the most inner
scope to the most outer scope looking for the label, performing any
necessary unwinds on the way. Once the label is found, it is then
jumped to.
`delete` has to operate directly on Reference Records, so this
introduces a new set of operations called DeleteByValue, DeleteVariable
and DeleteById. They operate similarly to their Get counterparts,
except they end in creating a (temporary) Reference and calling delete_
on it.
When calling emit_load_from_reference with a MemberExpression, it is
only necessary to store the result of evaluating MemberExpression's
object when performing computed property lookup.
This allows us to skip unnecessary stores for identifier lookup.
For example, this would generate 3 unnecessary stores:
```
> Temporal.PlainDateTime.prototype.add
JS::Bytecode::Executable (REPL)
1:
[ 0] GetVariable 0 (Temporal)
[ 28] Store $2
[ 30] GetById 1 (PlainDateTime)
[ 40] Store $3
[ 48] GetById 2 (prototype)
[ 58] Store $4
[ 60] GetById 3 (add)
```
With this, it generates:
```
> Temporal.PlainDateTime.prototype.add
JS::Bytecode::Executable (REPL)
1:
[ 0] GetVariable 0 (Temporal)
[ 28] GetById 1 (PlainDateTime)
[ 38] GetById 2 (prototype)
[ 48] GetById 3 (add)
```
The body of for/in/of can contain an unconditional block terminator
(e.g. return, throw), so we have to check for that before generating
the Jump to the loop update block.
NewArray now only contains two elements maximum in `m_elements` to
indicate the range of registers to create the array from.
However, `m_element_count` still contains how many registers are in the
range and the stringifier was not updated to account for this. Thus, if
the range contained more than 2 registers, it would do a read OOB on
`m_elements`.
This makes it now just print the first and second entries in
`m_elements` in the format of `[<reg>-<reg>]`.
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.