@@ -7,7 +7,7 @@ authors: boa-dev
7
7
8
8
In this post we dive into how ECMAScript engines store variables.
9
9
We go over storage optimizations and learn about scope analysis.
10
- If you are a ECMAScript developer you will get some practical tips to improve the performance of your code.
10
+ If you are an ECMAScript developer you will get some practical tips to improve the performance of your code.
11
11
If you write your own ECMAScript engine or any interpreter or compiler you might get some implementation ideas.
12
12
13
13
<!-- truncate-->
@@ -18,7 +18,7 @@ Before we start, let's get some disclaimers out of the way:
18
18
Other engines might do things different, so take everything with a grain of salt.
19
19
- This post omits some implementation details to focus on the most relevant parts.
20
20
- This post contains data structures written in pseudo Rust.
21
- These are only for visualization and you do not need to understand Rust.
21
+ These are only for visualization, and you do not need to understand Rust.
22
22
23
23
## Scopes and Variables
24
24
@@ -60,7 +60,7 @@ const a = 1;
60
60
```
61
61
62
62
In this example our two variables have different identifiers.
63
- Notice that when we access the variable ` a ` from the block scope, it's value is resolved as expected.
63
+ Notice that when we access the variable ` a ` from the block scope, its value is resolved as expected.
64
64
This is because scopes are nested.
65
65
When we cannot find a variable in the current scope, we look for the same identifier in the outer scope.
66
66
In this case we have to look for ` a ` in the block scope and then in the global scope.
@@ -91,7 +91,7 @@ console.log(a); // 1
91
91
You can see that variables are tied to their scopes.
92
92
All three variables ` a ` never change their values.
93
93
They just exist in their respective scopes and as soon as the scope has ended they are no longer accessible.
94
- Instead the previous outer scope returns to being the current scope and it's variables are accessible.
94
+ Instead, the previous outer scope returns to being the current scope and its variables are accessible.
95
95
96
96
You can see that in addition to blocks, functions also have scopes.
97
97
There are some more details to function scopes and how ` let ` , ` const ` and ` var ` differ.
@@ -118,10 +118,10 @@ struct Scope {
118
118
```
119
119
120
120
This is a nice and easy data structure for our variables.
121
- And because most languages come with a hashmap builtin , we do not have implement much!
121
+ And because most languages come with a hashmap built-in , we do not have implement much!
122
122
123
123
Let's add the ability to nest our scopes.
124
- Since all scopes are the same, we can just build a self referential data structure:
124
+ Since all scopes are the same, we can just build a self- referential data structure:
125
125
126
126
``` rust
127
127
struct Scope {
@@ -138,7 +138,7 @@ This was the approach we used in Boa before we switched to a different implement
138
138
139
139
You may already have spotted some performance issues with this data structure.
140
140
Consider that accessing variables is one of the things happening all the time in most languages.
141
- Therefore the runtime performance of variable access operations should be highly optimized.
141
+ Therefore, the runtime performance of variable access operations should be highly optimized.
142
142
With this current data structure we have to perform at least one hashmap lookup per variable access.
143
143
Most hashmap implementations will incur significant cost compared to accessing a fixed location in memory.
144
144
This problem gets worse when the variable we want to access is not in our current scope.
@@ -148,11 +148,11 @@ How would you optimize this data structure for runtime performance?
148
148
Can you find a way to locate each variable without accessing multiple hashmaps?
149
149
150
150
When we read code, we can use our mental model of variables and scopes to see how each variable is unique.
151
- We just have apply that knowledge to our data structure.
152
- In practice we can assign each variable two indices that make it unique and give it a defined location in memory:
151
+ We just have to apply that knowledge to our data structure.
152
+ In practice, we can assign each variable two indices that make it unique and give it a defined location in memory:
153
153
154
154
- ` scope index ` : The index of the scope that the variable is declared in.
155
- - ` variable index ` : The index of the variable in it's scope.
155
+ - ` variable index ` : The index of the variable in its scope.
156
156
157
157
Let's visualize this in an example:
158
158
@@ -196,12 +196,12 @@ struct Scope {
196
196
}
197
197
```
198
198
199
- Instead of having a self referential data structure, we now have a two dimensional array.
199
+ Instead of having a self- referential data structure, we now have a two- dimensional array.
200
200
201
201
While this is our runtime data structure, we still have to calculate the variable indices before running the code.
202
202
For that we can use our previous approach with some slight modifications.
203
- Instead of storing the value of the variable, we just store it's index.
204
- Additionally we store an index in every scope to easily access our scope indices:
203
+ Instead of storing the value of the variable, we just store its index.
204
+ Additionally, we store an index in every scope to easily access our scope indices:
205
205
206
206
``` rust
207
207
struct Scope {
@@ -215,7 +215,7 @@ struct Variable {
215
215
}
216
216
```
217
217
218
- While this data structure still works based on self referential hashmaps, we only need it before running code.
218
+ While this data structure still works based on self- referential hashmaps, we only need it before running code.
219
219
Instead of doing a lookup on every variable access at runtime, we just have to do it once.
220
220
221
221
## Local Variables
@@ -233,18 +233,18 @@ function addOne(a) {
233
233
addOne (2 );
234
234
```
235
235
236
- Currently we store ` a ` and ` one ` in our scopes and access them when performing the addition.
236
+ Currently, we store ` a ` and ` one ` in our scopes and access them when performing the addition.
237
237
But why do we need the special data structure for variables and scopes at all?
238
238
What if we could just store the variables directly where we need them?
239
239
240
240
A typical ECMAScript engine uses a virtual machine (VM) to execute your code.
241
241
VMs use dedicated memory for values they operate on; a stack or registers.
242
- For the purpose of this post, we use registers but the stack would work in the same way.
242
+ For the purpose of this post, we use registers, but the stack would work in the same way.
243
243
Let's try to use registers to store variables.
244
244
While compiling the ECMAScript code into operations for our VM we assign each variable to a register.
245
245
Then we modify our variable operations to use registers instead of scopes to access variables.
246
246
247
- When we test our example from above, it works fine with this changes.
247
+ When we test our example from above, it works fine with these changes.
248
248
Let's write down what exactly happens:
249
249
250
250
1 . The function ` addOne ` is called. Registers for ` a ` and ` one ` are allocated.
@@ -297,8 +297,8 @@ To determine which variables can be stored in registers, we analyze them prior t
297
297
We can reuse our previously established scope structure based on hashmaps.
298
298
It just needs some additional information to make our analysis work.
299
299
Each scope needs to be flagged to indicate if it is a function scope.
300
- This is important, because we have to track if a variable is every accessed from a nested function.
301
- Additionally each variable needs a flag to indicate if it can be a local variable.
300
+ This is important, because we have to track if a variable is ever accessed from a nested function.
301
+ Additionally, each variable needs a flag to indicate if it can be a local variable.
302
302
303
303
Our adjusted scope structure looks like this:
304
304
@@ -390,7 +390,7 @@ When we encounter a variable that cannot be local, we use or old VM operations f
390
390
### Other Exceptions
391
391
392
392
There are some more situations that prevent us from using local variables.
393
- We have to account for every case where a variable might be accessed from outside it's function.
393
+ We have to account for every case where a variable might be accessed from outside its function.
394
394
Without going into detail on each of these cases, we can find all of them via scope analysis.
395
395
396
396
Here is a quick overview:
@@ -451,7 +451,7 @@ Here is a quick overview:
451
451
452
452
In the first loop execution ` a1` is the variable.
453
453
In the second loop execution ` a1` is the object property.
454
- As a result of this behavior, every variable accessed inside of a ` with` statement cannot be local.
454
+ As a result of this behavior, every variable accessed inside a ` with` statement cannot be local.
455
455
456
456
## Conclusion
457
457
0 commit comments