Commit graph

247 commits

Author SHA1 Message Date
Ali Mohammad Pur
6ab6321c2f LibJS: Remove redundant const_cast
The accessed field is mutable, so there's no need for this const_cast.
2021-10-08 12:25:24 +02:00
Andreas Kling
41a072bded LibJS: Fast non-local variable access :^)
This patch introduces the "environment coordinate" concept, which
encodes the distance from a variable access to the binding it ends up
resolving to.

EnvironmentCoordinate has two fields:

    - hops:  The number of hops up the lexical environment chain we have
             to make before getting to the resolved binding.

    - index: The index of the resolved binding within its declarative
             environment record.

Whenever a variable lookup resolves somewhere inside a declarative
environment, we now cache the coordinates and reuse them in subsequent
lookups. This is achieved via a coordinate cache in JS::Identifier.

Note that non-strict direct eval() breaks this optimization and so it
will not be performed if the resolved environment has been permanently
screwed by eval().

This makes variable access *significantly* faster. :^)
2021-10-07 11:53:18 +02:00
Linus Groh
8074bdc049 LibJS: Skip declarative env in block statement without lexical decls
The idea here is simple: If the block statement doesn't contain any
lexical declarations, we don't need to allocate, initialize and
eventually garbage collect a new declarative environment.
This even makes lookups across nested blocks slightly faster as we don't
have to traverse a chain of empty environments anymore - instead, the
execution context just stores the outermost non-empty one.

This doesn't speed up test-js considerably, but has a noticeable effect
on test262 and real-world web content :^)
2021-10-05 14:52:53 +02:00
Linus Groh
4fa5748093 LibJS: Add an optimization to avoid needless arguments object creation
This gives FunctionNode a "might need arguments object" boolean flag and
sets it based on the simplest possible heuristic for this: if we
encounter an identifier called "arguments" or "eval" up to the next
(nested) function declaration or expression, we won't need an arguments
object. Otherwise, we *might* need one - the final decision is made in
the FunctionDeclarationInstantiation AO.

Now, this is obviously not perfect. Even if you avoid eval, something
like `foo.arguments` will still trigger a false positive - but it's a
start and already massively cuts down on needlessly allocated objects,
especially in real-world code that is often minified, and so a full
"arguments" identifier will be an actual arguments object more often
than not.

To illustrate the actual impact of this change, here's the number of
allocated arguments objects during a full test-js run:

Before:
- Unmapped arguments objects: 78765
- Mapped arguments objects: 2455

After:
- Unmapped arguments objects: 18
- Mapped arguments objects: 37

This results in a ~5% speedup of test-js on my Linux host machine, and
about 3.5% on i686 Serenity in QEMU (warm runs, average of 5).

The following microbenchmark (calling an empty function 1M times) runs
25% faster on Linux and 45% on Serenity:

    function foo() {}
    for (var i = 0; i < 1_000_000; ++i)
        foo();

test262 reports no changes in either direction, apart from a speedup :^)
2021-10-05 10:15:14 +01:00
Linus Groh
e5b8544762 LibJS: Convert enumerable_own_property_names() to ThrowCompletionOr 2021-10-03 20:14:03 +01:00
Linus Groh
fe86b04b42 LibJS: Convert define_property_or_throw() to ThrowCompletionOr 2021-10-03 20:14:03 +01:00
Linus Groh
364dd42fc8 LibJS: Convert create_data_property_or_throw() to ThrowCompletionOr 2021-10-03 20:14:03 +01:00
Linus Groh
b7e5f08e56 LibJS: Convert Object::get() to ThrowCompletionOr
To no one's surprise, this patch is pretty big - this is possibly the
most used AO of all of them. Definitely worth it though.
2021-10-03 20:14:03 +01:00
davidot
1bc945860d Everywhere: Use my awesome new serenityos email :^) 2021-10-03 13:53:47 +01:00
davidot
e5d48ee238 LibJS: Fix switch skipping case evaluation when hitting the default case
When no case match we should not just execute the statements of the
default case but also of any cases below the default case.
2021-09-30 08:16:32 +01:00
davidot
830ea0414c LibJS: Make scoping follow the spec
Before this we used an ad-hoc combination of references and 'variables'
stored in a hashmap. This worked in most cases but is not spec like.
Additionally hoisting, dynamically naming functions and scope analysis
was not done properly.

This patch fixes all of that by:
  - Implement BindingInitialization for destructuring assignment.
  - Implementing a new ScopePusher which tracks the lexical and var
    scoped declarations. This hoists functions to the top level if no
    lexical declaration name overlaps. Furthermore we do checking of
    redeclarations in the ScopePusher now requiring less checks all over
    the place.
  - Add methods for parsing the directives and statement lists instead
    of having that code duplicated in multiple places. This allows
    declarations to pushed to the appropriate scope more easily.
  - Remove the non spec way of storing 'variables' in
    DeclarativeEnvironment and make Reference follow the spec instead of
    checking both the bindings and 'variables'.
  - Remove all scoping related things from the Interpreter. And instead
    use environments as specified by the spec. This also includes fixing
    that NativeFunctions did not produce a valid FunctionEnvironment
    which could cause issues with callbacks and eval. All
    FunctionObjects now have a valid NewFunctionEnvironment
    implementation.
  - Remove execute_statements from Interpreter and instead use
    ASTNode::execute everywhere this simplifies AST.cpp as you no longer
    need to worry about which method to call.
  - Make ScopeNodes setup their own environment. This uses four
    different methods specified by the spec
    {Block, Function, Eval, Global}DeclarationInstantiation with the
    annexB extensions.
  - Implement and use NamedEvaluation where specified.

Additionally there are fixes to things exposed by these changes to eval,
{for, for-in, for-of} loops and assignment.

Finally it also fixes some tests in test-js which where passing before
but not now that we have correct behavior :^).
2021-09-30 08:16:32 +01:00
davidot
79caca8ca2 LibJS: Allow multiple labels on the same statement
Since there are only a number of statements where labels can actually be
used we now also only store labels when necessary.
Also now tracks the first continue usage of a label since this might not
be valid but that can only be determined after we have parsed the
statement.
Also ensures the correct error does not get wiped by load_state.
2021-09-30 08:16:32 +01:00
davidot
bfc1b4ba61 LibJS: Allow member expressions in binding patterns
Also allows literal string and numbers as property names in object
binding patterns.
2021-09-30 08:16:32 +01:00
Linus Groh
8c81c84c18 LibJS: Convert internal_set_prototype_of() to ThrowCompletionOr 2021-09-29 23:49:53 +01:00
Linus Groh
5148150e1c LibJS: Convert internal_get_prototype_of() to ThrowCompletionOr 2021-09-29 23:49:53 +01:00
Andreas Kling
3252d984ae LibJS: Allow statements to have multiple labels
This is a curious thing that occurs more often than you'd think in
minified JavaScript:

    a: b: c: for (...) { ... break b; ... }
2021-09-26 18:24:19 +02:00
Linus Groh
ababcc5725 LibJS: Defer execution of switch default clause until after case clauses
When we encounter a default clause in a switch statement, we should not
execute it immediately, instead we need to wait until all case clauses
have been executed as a matching case clause can break from the
switch/case.

The code is nowhere close to the spec, so instead of fixing it properly
I just made it slightly worse, but correct. Needs a complete refactor at
some point.
2021-09-26 18:04:25 +02:00
Linus Groh
38157a6093 LibJS: Move has_constructor() from NativeFunction to FunctionObject
At a later point this will indicate whether some FunctionObject "has a
[[Construct]] internal method" (separate from the current FunctionObject
call() / construct()), to help with a more spec-compliant implementation
of [[Call]] and [[Construct]].
This means that it is no longer relevant to just NativeFunction.
2021-09-25 17:51:30 +02:00
Linus Groh
136451c3af LibJS: Move [[HomeObject]] to ECMAScriptFunctionObject 2021-09-25 17:51:30 +02:00
Linus Groh
06726d41ac LibJS: Move [[ConstructorKind]] to ECMAScriptFunctionObject 2021-09-25 17:51:30 +02:00
Linus Groh
e37cf73300 LibJS: Rename OrdinaryFunctionObject to ECMAScriptFunctionObject
The old name is the result of the perhaps somewhat confusingly named
abstract operation OrdinaryFunctionCreate(), which creates an "ordinary
object" (https://tc39.es/ecma262/#ordinary-object) in contrast to an
"exotic object" (https://tc39.es/ecma262/#exotic-object).

However, the term "Ordinary Function" is not used anywhere in the spec,
instead the created object is referred to as an "ECMAScript Function
Object" (https://tc39.es/ecma262/#sec-ecmascript-function-objects), so
let's call it that.

The "ordinary" vs. "exotic" distinction is important because there are
also "Built-in Function Objects", which can be either implemented as
ordinary ECMAScript function objects, or as exotic objects (our
NativeFunction).

More work needs to be done to move a lot of infrastructure to
ECMAScriptFunctionObject in order to make FunctionObject nothing more
than an interface for objects that implement [[Call]] and optionally
[[Construct]].
2021-09-25 17:51:30 +02:00
Linus Groh
32932f83be LibJS: Rename {Abstract,Typed => Loosely,Strictly}{Equals,Inequals}
This affects the AST's BinaryOp enum as well as the Bytecode's
ENUMERATE_BYTECODE_OPS and JS_ENUMERATE_COMMON_BINARY_OPS macros.
2021-09-24 09:13:57 +02:00
Linus Groh
580a7e0f7c LibJS: Rename abstract_eq() to is_loosely_equal()
This got turned into a proper AO with a new name recently.

See: c7d6d1c
2021-09-24 09:13:57 +02:00
Linus Groh
c7ff89891c LibJS: Rename strict_eq() to is_strictly_equal()
This got turned into a proper AO with a new name recently.

See: 19d7ca4
2021-09-24 09:13:57 +02:00
Idan Horowitz
ab594e5f2f LibJS: Convert Value::invoke and VM::call to ThrowCompletionOr 2021-09-23 23:59:13 +03:00
Idan Horowitz
1da8faebf5 LibJS: Convert perform_eval to ThrowCompletionOr 2021-09-21 23:28:38 +03:00
Idan Horowitz
02a88c5063 LibJS: Convert make_super_property_reference to ThrowCompletionOr 2021-09-21 23:28:38 +03:00
Linus Groh
568296d0cc LibJS: Use ThrowCompletionOr in require_object_coercible() 2021-09-15 23:46:53 +01:00
Ali Mohammad Pur
72ddaa31e3 LibJS: Implement parsing and execution of optional chains 2021-09-14 20:03:27 +01:00
Ali Mohammad Pur
4f7e14e0aa LibJS: Reorder the global eval function call detection conditions a bit
This just makes it clearer, since the actual check is off-screen, making
the reader wonder what that check is for.
2021-09-14 20:03:27 +01:00
Linus Groh
99f9609e45 LibJS: Evaluate function arguments before checking callee type
In the spec, this happens in the EvaluateCall abstract operation
(https://tc39.es/ecma262/#sec-evaluatecall), and the order is defined
as:

    3. Let argList be ? ArgumentListEvaluation of arguments.
    4. If Type(func) is not Object, throw a TypeError exception.
    5. If IsCallable(func) is false, throw a TypeError exception.

In LibJS this is handled by CallExpression::execute(), which had the
callee function check first and would therefore never evaluate the
arguments for a non-function callee.
2021-09-13 17:44:08 +01:00
Andreas Kling
0d2c3f62d3 LibJS: Use move semantics more when creating Reference objects
Turns a bunch of FlyString copies into moves.
2021-09-11 20:38:45 +02:00
davidot
db0a48d34c LibJS: Restore the environment if an exception is thrown in 'with' block 2021-09-08 20:37:39 +01:00
Ali Mohammad Pur
97e97bccab Everywhere: Make ByteBuffer::{create_*,copy}() OOM-safe 2021-09-06 01:53:26 +02:00
davidot
def8b44c40 LibJS: Add support for public fields in classes 2021-09-01 13:39:14 +01:00
davidot
020bfc9d93 LibJS: Parse and partially execute import and export statements
We produce the import and export entries as per the spec. However we do
not yet verify that named things that are exported are declared
somewhere.
2021-08-15 23:51:47 +01:00
Timothy Flynn
f1dd770a8a LibJS: Parse RegExp literals at AST creation time, not execution time
The spec requires that invalid RegExp literals must cause a Syntax Error
before the JavaScript is executed. See:
https://tc39.es/ecma262/#sec-patterns-static-semantics-early-errors

This is explicitly tested in the RegExp/property-escapes test262 tests.
For example, see unsupported-property-Line_Break.js:

    $DONOTEVALUATE();
    /\p{Line_Break}/u;

That RegExp literal is invalid because Line_Break is not a supported
Unicode property. $DONOTEVALUATE() just throws an exception when it is
executed. The test expects that this file will fail to be parsed.

Note that RegExp patterns can still be parsed at execution time by way
of "new RegExp(...)".
2021-07-30 21:26:31 +01:00
Timothy Flynn
c6e9c6d6ab LibJS: Follow the spec more closely when determining the this value
Co-authored-by: davidot <david.tuin@gmail.com>
2021-07-20 23:45:28 +02:00
davidot
a394aa5830 LibJS: Fix that vm.in_strict_mode was propagated to eval and functions
For eval it depends on the CallerMode and for a created function it
depends on the function itself.
2021-07-20 23:45:28 +02:00
Ali Mohammad Pur
77a5144264 LibJS: Add support for binding patterns in catch clauses
`try { ... } catch({a=foo}) {}` is valid, and now we parse and evaluate
it correctly :^)
2021-07-11 21:41:54 +01:00
Ali Mohammad Pur
1a9518ebe3 LibJS: Implement parsing and evaluation for AssignmentPatterns
e.g. `[...foo] = bar` can now be evaluated :^)
2021-07-11 21:41:54 +01:00
Hendi
0dc4e722e6 LibJS: Make FunctionExpression more spec-compliant 2021-07-07 23:31:51 +01:00
Idan Horowitz
306d59276a LibJS: Stop using a native property for RegExp's lastIndex property
This is not a functional change, the exposed (incorrect) behaviour is
the same as it was before, this simply removes the last user of
NativeProperties, allowing us to remove them completely from LibJS.
2021-07-07 21:47:22 +01:00
Hendi
37c4fbb6ca LibJS: Don't hoist functions under certain circumstances
When a lexical declaration with the same name as a function exists,
the function is not hoisted (annex B).
2021-07-06 22:55:16 +01:00
Linus Groh
0ba81dc0b7 LibJS: Remove Object::is_array() in favor of Value::is_array() and RTTI
It's way too easy to get this wrong: for the IsArray abstract operation,
Value::is_array() needs to be called. Since we have RTTI, the virtual
Object::is_array() method is not needed anymore - if we need to know
whether something is *actually* a JS::Array (we currently check in more
cases than we should, I think) and not a Proxy with an Array target, we
should do that in a way that doesn't look like an abstract operation.
2021-07-06 14:26:18 +01:00
Idan Horowitz
a6b8291a9b LibJS: Add define_direct_property and remove the define_property helper
This removes all usages of the non-standard define_property helper
method and replaces all it's usages with the specification required
alternative or with define_direct_property where appropriate.
2021-07-06 14:20:30 +01:00
Linus Groh
3faeabf1dc Revert "LibJS: Don't hoist functions under certain circumstances"
This reverts commit 3411d50737.

It was causing LeakSanitizer on CI to fail, possibly due to a circular
reference.
2021-07-06 13:25:37 +01:00
Hendi
3411d50737 LibJS: Don't hoist functions under certain circumstances
When a lexical declaration with the same name as a function exists,
the function is not hoisted (annex B).
2021-07-06 00:15:37 +01:00
Hendi
38fd980b0c LibJS: Improve function hoisting across blocks
The parser now keeps track of a scope chain so that it can hoist
function declarations to the closest function scope.
2021-07-06 00:15:37 +01:00
Hendi
72f8d90dc5 LibJS: Remove variables from FunctionNode
They weren't consumed anywhere outside the AST and went
against the usual concept of having declaration in ScopeNode.
2021-07-06 00:15:37 +01:00