The steps for creating a DeclarativeEnvironment for each iteration of a
for-loop can be done equivalently to the spec without following the spec
directly. For each binding creating in the loop's init expression, we:
1. Create a new binding in the new environment.
2. Grab the current value of the binding in the old environment.
3. Set the value in the new environment to the old value.
This can be replaced by initializing the bindings vector in the new
environment directly with the bindings in the old environment (but only
copying the bindings of the init statement).
Similar to the direct getter and setter in DeclarativeEnvironment, there
are cases where we already know the index of a binding and can avoid a
O(n) lookup to re-find that index.
This reduces the size of the DeclarativeEnvironment from 72 bytes to 48
bytes. This savings helps in the context of nested for-loops which use
'let' to bind the initial variable declarations. In this case, the spec
dicates we create a new environment for each loop iteration by way of
the CreatePerIterationEnvironment AO.
In particular, test262's generated RegExp tests contains many loops of
the form:
for (let i = 0; i < a_number_on_the_order_of_10; ++i)
for (let j = 0; j < a_number_on_the_order_of_10_thousand; ++j)
This results in creating hundreds of thousands of environments.
Constructing the HashMap in DeclarativeEnvironment was by far the most
expensive thing when making JavaScript function calls.
As it turns out, we don't really need this to be a HashMap in the first
place, as lookups are cached (by EnvironmentCoordinate) after the first
access, so after that we were not even looking in the HashMap, going
directly to the bindings Vector instead.
This reduces function_declaration_instantiation() from 16% to 9% when
idling in "Biolab Disaster". It also reduces has_binding() from 3% to
1% on the same content.
With these changes, we now actually get to idle a little bit between
game frames on my machine. :^)
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 patch adds two DeclarativeEnvironment APIs:
- get_binding_value_direct()
- set_mutable_binding_direct()
These work identically to their non-direct-suffixed counterparts, but
take an index instead of a bound name. This will allow someone who has
a binding index to get/set that binding directly without any additional
hash lookups.
The previous storage for DeclarativeEnvironment looked like this:
HashMap<FlyString, Binding> m_bindings;
This patch changes that to:
HashMap<FlyString, size_t> m_names;
Vector<Binding> m_bindings;
The main goal here is to give each binding an index that can ultimately
be cached and used for optimized environment accesses.
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 :^).
This code is non-conforming and will eventually get cleaned out once
we implement proper variable bindings. However, this will aid us in
improving other parts of the code right now.