Skip to content

Add Url Searchparams to ConformanceView #197

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 5, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 82 additions & 79 deletions blog/2025-03-05-local-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ title: "How ECMAScript Engines Optimize Your Variables"
authors: boa-dev
---

In this post, we will dive into how ECMAScript engines store variables,
In this post, we will dive into how ECMAScript engines store variables,
go over storage optimizations, and learn about scope analysis.
If you are an ECMAScript developer, you will get some practical tips to improve the performance of your code.
If you write your own ECMAScript engine or any interpreter/compiler, you might get some implementation ideas.
Expand Down Expand Up @@ -33,9 +33,10 @@ Let's look at an example to visualize scopes:
const a = 1;
console.log(a); // 1

{ // <- start of a block scope
const a = 2;
console.log(a); // 2
{
// <- start of a block scope
const a = 2;
console.log(a); // 2
} // <- end of a block scope
```

Expand All @@ -53,9 +54,9 @@ Let's modify our example to see what happens in that case:
const a = 1;

{
const b = 2;
console.log(a); // 1
console.log(b); // 2
const b = 2;
console.log(a); // 1
console.log(b); // 2
}
```

Expand All @@ -71,16 +72,18 @@ Let's look at a more complex example:
const a = 1;
console.log(a); // 1

function f() { // <- start of a function scope
var a = 2;
console.log(a); // 2
function f() {
// <- start of a function scope
var a = 2;
console.log(a); // 2

{ // <- start of a block scope
let a = 3;
console.log(a); // 3
} // <- end of a block scope
{
// <- start of a block scope
let a = 3;
console.log(a); // 3
} // <- end of a block scope

console.log(a); // 2
console.log(a); // 2
} // <- end of a function scope

f();
Expand All @@ -104,9 +107,9 @@ For our proposes we will only work with `let` and `const` and skip those details
When developing an ECMAScript engine we have to think about how we store and access scopes and variables.
Take a look at the requirements we have for that storage data structure:

* A variable maps an identifier to a value.
* A scope can have multiple variables with unique identifiers.
* A scope may have an outer scope.
- A variable maps an identifier to a value.
- A scope can have multiple variables with unique identifiers.
- A scope may have an outer scope.

The variables in a scope fit a typical key-value store, like a hashmap.
The hashmap stores our variable identifiers as keys and the variable values as corresponding values:
Expand Down Expand Up @@ -159,8 +162,8 @@ Let's visualize this in an example:
```js
const a = 1; // scope index: 0; variable index: 0
{
const b = 2; // scope index: 1; variable index: 0
const c = 3; // scope index: 1; variable index: 1
const b = 2; // scope index: 1; variable index: 0
const c = 3; // scope index: 1; variable index: 1
}
```

Expand All @@ -172,10 +175,10 @@ Let's explore how unique these indices have to be:

```js
{
const a = 1; // scope index: 1; variable index: 0
const a = 1; // scope index: 1; variable index: 0
}
{
const b = 2; // scope index: 1; variable index: 0
const b = 2; // scope index: 1; variable index: 0
}
```

Expand Down Expand Up @@ -227,8 +230,8 @@ Let's take a look at this example:

```js
function addOne(a) {
const one = 1;
return one + a;
const one = 1;
return one + a;
}
addOne(2);
```
Expand Down Expand Up @@ -260,10 +263,10 @@ Let's look at this example:

```js
function addOneBuilder() {
const one = 1;
return (a) => {
return one + a;
};
const one = 1;
return (a) => {
return one + a;
};
}
const addOne = addOneBuilder();
addOne(2);
Expand Down Expand Up @@ -327,10 +330,10 @@ Let's visualize the scope analysis by writing out the scopes for this example:

```js
function addOneBuilder() {
const one = 1;
return (a) => {
return one + a;
};
const one = 1;
return (a) => {
return one + a;
};
}
```

Expand Down Expand Up @@ -396,69 +399,69 @@ Without going into detail on each of these cases, we can find all of them via sc
Here is a quick overview:

- Non `strict` functions create a mapped `arguments` object.
The mapped `arguments` object can be used to read and write function arguments without using their identifiers.
The reads and writes are kept in sync with the values of the argument variables.
This means that we cannot determine if the argument variables are accessed from outside the function.

An example of such a situation would be this code:

```js
function f(a) {
console.log(a); // initial
(() => {
arguments[0] = "modified";
})()
console.log(a); // modified
}
f("initial");
```
The mapped `arguments` object can be used to read and write function arguments without using their identifiers.
The reads and writes are kept in sync with the values of the argument variables.
This means that we cannot determine if the argument variables are accessed from outside the function.

The solution here is to mark every argument variable that might be accessed through a mapped `arguments` object as non-local.
An example of such a situation would be this code:

- Direct calls to `eval` allow potential variable access.
Direct calls to `eval` have access to the current variables.
Since any code could be executed in `eval` we cannot do proper scope analysis on any variables in such cases.
```js
function f(a) {
console.log(a); // initial
(() => {
arguments[0] = "modified";
})();
console.log(a); // modified
}
f("initial");
```

An example of direct `eval` usage would be this:
The solution here is to mark every argument variable that might be accessed through a mapped `arguments` object as non-local.

```js
function f() {
const a = 1;
eval("function nested() {console.log(a)}; nested();");
}
f();
```
- Direct calls to `eval` allow potential variable access.
Direct calls to `eval` have access to the current variables.
Since any code could be executed in `eval` we cannot do proper scope analysis on any variables in such cases.

Our solution is this case is to mark every variable in the scopes where the direct `eval` call is as non-local.
An example of direct `eval` usage would be this:

- Usage of the `with` statement.
Variable identifiers inside a `with` statement are not static.
A variable identifier could be the access to a variable, but it also could be the access to an object property.
```js
function f() {
const a = 1;
eval("function nested() {console.log(a)}; nested();");
}
f();
```

See this example:
Our solution is this case is to mark every variable in the scopes where the direct `eval` call is as non-local.

```js
function f() {
const a1 = 1;
for (let i = 0; i < 2; i++) {
with ({ [`a${i}`]: 2 }) {
console.log(a1);
}
}
- Usage of the `with` statement.
Variable identifiers inside a `with` statement are not static.
A variable identifier could be the access to a variable, but it also could be the access to an object property.

See this example:

```js
function f() {
const a1 = 1;
for (let i = 0; i < 2; i++) {
with ({ [`a${i}`]: 2 }) {
console.log(a1);
}
}
f();
```
}
f();
```

In the first loop execution `a1` is the variable.
In the second loop execution `a1` is the object property.
As a result of this behavior, every variable accessed inside a `with` statement cannot be local.
In the first loop execution `a1` is the variable.
In the second loop execution `a1` is the object property.
As a result of this behavior, every variable accessed inside a `with` statement cannot be local.

## Conclusion

After implementing local variables in Boa, we saw significant performance improvements in our benchmarks.
Our overall benchmark scope improved by more than 25%.
In one specific benchmark the scope increased by over 70%.
Notice that Boa is not the most performant engine yet.
Notice that Boa is not the most performant engine yet.
There are probably other optimizations relating to variable storage that we have not implemented yet.

Hopefully, you might have already picked up some practical tips to potentially improve to performance of your ECMAScript code.
Expand Down
15 changes: 5 additions & 10 deletions blog/2025-06-15-temporal-impl-1.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
---
layout: post
tags: [post]
title:
Implementing Temporal, the new date/time API for JavaScript (and
title: Implementing Temporal, the new date/time API for JavaScript (and
Rust!)
metadata: ["temporal", "temporal_rs", "boa", "date/time"]
description:
A blog post about the temporal_rs Rust crate that implements
description: A blog post about the temporal_rs Rust crate that implements
JavaScript's Temporal API and how temporal_rs supports implementing
Temporal in JavaScript engines.
authors: boa-dev
Expand Down Expand Up @@ -472,12 +470,9 @@ A FFI version of temporal is also available for C and C++ via

The above issues are considered blocking for a 0.1.0 release.

[mdn-temporal]:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal
[mdn-temporal]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal
[temporal-rs-repo]: https://github.com/boa-dev/temporal
[construct-link]:
https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-ecmascript-function-objects-construct-argumentslist-newtarget
[call-link]:
https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-ecmascript-function-objects-call-thisargument-argumentslist
[construct-link]: https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-ecmascript-function-objects-construct-argumentslist-newtarget
[call-link]: https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-ecmascript-function-objects-call-thisargument-argumentslist
[boa-test262]: https://test262.fyi/#|boa
[temporal-capi]: https://crates.io/crates/temporal_capi
40 changes: 17 additions & 23 deletions src/components/benchmarks/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const ChartOptions = {

export const BenchmarkGraphs: React.FC<BenchmarkGraphsProps> = ({
selectedEngines,
range
range,
}) => {
// Control the state of which engines are displayed using a Set

Expand All @@ -52,20 +52,20 @@ export const BenchmarkGraphs: React.FC<BenchmarkGraphsProps> = ({
const colorMode = useColorMode();
ChartJS.defaults.color = colorMode.colorMode === "light" ? "#666" : "white";


useEffect(() => {
fetch(`https://boa-api.jason-williams.co.uk/benchmarks?months=${range}&engines=${selectedEngines.join(',')}`).then(
(res) => res.json())
.then(respData => {
setData(respData)
});
fetch(
`https://boa-api.jason-williams.co.uk/benchmarks?months=${range}&engines=${selectedEngines.join(",")}`,
)
.then((res) => res.json())
.then((respData) => {
setData(respData);
});
}, [selectedEngines, range]);

useEffect(() => {
setCharts(buildChartFromBenchmark(data));
}, [data]);


return charts && charts.map((chart) => chart);
};

Expand All @@ -74,19 +74,18 @@ const normalizeBenchmarkData = (benchmarkData: any[]) => {
new Date(entry.date).toLocaleDateString(),
);

const engines = Object.keys(benchmarkData[0]).filter(key => key != "date");
const engines = Object.keys(benchmarkData[0]).filter((key) => key != "date");

return {
labels,
datasets: engines.map(engine => ({
datasets: engines.map((engine) => ({
label: engine,
data: benchmarkData.map(entry => entry[engine]),
fill: false
data: benchmarkData.map((entry) => entry[engine]),
fill: false,
})),
};
};


const getBarChartData = (data) => {
// We only want the last value from each dataset
return {
Expand All @@ -101,12 +100,11 @@ const getBarChartData = (data) => {
};

const buildChartFromBenchmark = (data: any): any[] => {

let charts = [];
for (const benchmark in data) {
const normalizedData = normalizeBenchmarkData(data[benchmark])
const normalizedData = normalizeBenchmarkData(data[benchmark]);
const barData = getBarChartData(normalizedData);
charts.push((
charts.push(
<div key={benchmark}>
<div className={`card__header ${styles["benchmark-card-header"]}`}>
<Heading as="h2">{benchmark}</Heading>
Expand All @@ -119,13 +117,9 @@ const buildChartFromBenchmark = (data: any): any[] => {
<Bar data={barData} options={ChartOptions}></Bar>
</div>
</div>
</div>
));
};
</div>,
);
}

return charts;
};




2 changes: 2 additions & 0 deletions src/components/conformance/HeroBanner/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import {
createState,
mapToTestStats,
createSearchParams,
} from "@site/src/components/conformance/utils";
import { useHistory } from "@docusaurus/router";
import Heading from "@theme/Heading";
Expand Down Expand Up @@ -86,6 +87,7 @@ function BannerCard(props) {
className="button button--block button--primary"
onClick={() =>
history.push({
search: createSearchParams(props.item),
pathname: "/conformance",
state: createState(props.item),
})
Expand Down
Loading