Skip to content

Commit 01c3ef0

Browse files
committed
feat: add sections for JobExecutor, ModuleLoader and async changes
1 parent 292247f commit 01c3ef0

File tree

5 files changed

+187
-31
lines changed

5 files changed

+187
-31
lines changed
20.9 KB
Loading
171 KB
Loading

blog/boa-release-21/index.md

Lines changed: 152 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Boa makes it easy to embed a JS engine in your projects, and you can
1414
even use it from WebAssembly. See the [about](/about) page for more
1515
info.
1616

17-
In this release, our conformance has grown from 89.92% to 93.94% in the
17+
In this release, our conformance has grown from 89.92% to 94.12% in the
1818
official ECMAScript Test Suite (Test262). Our growth in conformance is
1919
driven by increased conformance for Temporal (discussed further below)
2020
with the rest of the development effort being focused on performance,
@@ -34,47 +34,159 @@ information on conformance [here][conformance].
3434

3535
There has been a lot of progress made on Temporal, the new Stage 3
3636
date/time proposal. With this release, Boa's conformance on Temporal
37-
grew from 40.67% to ~97%.
37+
grew from 40.67% to ~97%. This implementation is backed by the `temporal_rs`
38+
date/time Rust library, which we went over in our announcement
39+
[blog post](../2025-09-24-temporal-release). Give the post a read if you are
40+
interested in `temporal_rs` and its development history.
3841

39-
The implementation is backed by the `temporal_rs` date/time Rust
40-
library, which we went over briefly in our June
41-
[blog post](../2025-06-15-temporal-impl-1.md) with hopefully another post
42-
in the not too distant future. So far, `temporal_rs` has also been used in
43-
both V8 and Keisel to implement Temporal.
42+
### Span nodes and error backtraces
4443

45-
Temporal can be used from `boa_cli` or enabled in `boa_engine` with the
46-
`experimental` or `temporal` feature.
44+
We added support for storing spans in our AST nodes, which allows determining the
45+
exact location of an AST node on its original file. We already kind of
46+
supported this feature in our lexer, but we did not store the spans after parsing.
4747

48-
You can also try out Temporal directly in Rust using `temporal_rs`:
48+
Why is this important? Well, as a direct result from this, Boa now supports error backtraces
49+
when an exception is thrown!
4950

50-
```
51-
cargo add temporal_rs
51+
![backtrace-example](./img/backtrace.gif)
52+
53+
As an additional plus, you can enable the `native-backtrace` feature to include
54+
"native" functions on a backtrace.
55+
56+
![native-backtrace-example](./img/native-backtrace.gif)
57+
58+
This feature has been one of the most requested ones for years,
59+
and we hope it will
60+
greatly help with debugging errors when using Boa.
61+
62+
### Async APIs enhancements
63+
64+
Historically, hooking functions returning a `Future` into Boa has been one of the
65+
biggest pain points of our API. This was mostly caused by how we defined
66+
`FutureJob`:
67+
68+
```rust
69+
pub type FutureJob = Pin<Box<dyn Future<Output = NativeJob> + 'static>>;
5270
```
5371

54-
#### Special thanks
72+
With this definition, it was pretty much impossible to capture the `Context`
73+
inside the future, and functions that needed to interweave engine operations
74+
with awaiting `Future`s would have to be split into multiple parts:
75+
76+
```rust
77+
let fetch = async move {
78+
let body: Result<_, isahc::Error> = async {
79+
let mut response = Request::get(&url)
80+
.body(())?
81+
.send_async()
82+
.await?;
83+
84+
Ok(response.text().await?)
85+
}
86+
.await;
87+
88+
// Since the async context cannot take the `context` by ref, we have to continue
89+
// parsing inside a new `NativeJob` that will be enqueued into the promise job queue.
90+
NativeJob::new(move |context| -> JsResult<JsValue> {
91+
parse(body).await;
92+
93+
// Also needed to match `NativeJob::new`.
94+
Ok(JsValue::undefined())
95+
})
96+
};
97+
98+
// Just enqueue the future for now. We'll advance all the enqueued futures inside our custom
99+
// `JobQueue`.
100+
context
101+
.job_queue()
102+
.enqueue_future_job(Box::pin(fetch), context)
103+
}
104+
```
55105

56-
Special thanks to all the student's from University of Bergen who helped contribute to
57-
`temporal_rs` and Boa, as well as Shane Carr ([@sffc](https://github.com/@sffc)) and
58-
Manish Goregaokar ([@manishearth](https://github.com/@manishearth)) for their contributions
59-
and help with `temporal_rs`.
106+
We wanted to improve this API, and the solution we thought about was to make
107+
`Context` shareable by wrapping it using `RefCell`. However, this proved to be
108+
very difficult for two reasons:
109+
1. We needed to change all definitions to take `&RefCell<Context>` instead
110+
of `&mut Context`, which meant changing pretty much the whole codebase.
111+
2. Some of our VM code was reentrant, which meant calling `RefCell::borrow_mut`
112+
would cause panics in the reentrant parts of the code; we would need to
113+
redesign some parts of the engine to remove the reentrancy.
114+
115+
After putting a lot of thought on this, we came up with a really nice solution;
116+
instead of wrapping `Context` with `RefCell`, we would wrap `&mut Context` with
117+
`RefCell`, and only on the async-related APIs. This would allow not only capturing
118+
the context to `Future`-related functions, but also doing this without having to
119+
refactor big parts of the code. Thus, we ditched `FutureJob` and introduced a new
120+
type of job: `NativeAsyncJob`.
121+
122+
```Rust
123+
/// An ECMAScript [Job] that can be run asynchronously.
124+
///
125+
/// This is an additional type of job that is not defined by the specification, enabling running `Future` tasks
126+
/// created by ECMAScript code in an easier way.
127+
#[allow(clippy::type_complexity)]
128+
pub struct NativeAsyncJob {
129+
f: Box<dyn for<'a> FnOnce(&'a RefCell<&mut Context>) -> BoxedFuture<'a>>,
130+
realm: Option<Realm>,
131+
}
132+
```
60133

61-
### Span nodes and error backtraces
134+
With this change, any API that integrates with `Future` can additionally capture
135+
the `&RefCell<&mut Context>` to run engine-related operations after awaiting on
136+
a `Future`.
137+
138+
### Revamped `JobQueue`
139+
140+
After introducing the new job type, changes had to be made on
141+
[`JobQueue`](https://docs.rs/boa_engine/0.20.0/boa_engine/job/trait.JobQueue.html)
142+
to better support new job types. Thus, `JobQueue` was revamped and renamed to be the
143+
new `JobExecutor`:
144+
145+
```rust
146+
/// An executor of `ECMAscript` [Jobs].
147+
///
148+
/// This is the main API that allows creating custom event loops.
149+
///
150+
/// [Jobs]: https://tc39.es/ecma262/#sec-jobs
151+
pub trait JobExecutor: Any {
152+
/// Enqueues a `Job` on the executor.
153+
///
154+
/// This method combines all the host-defined job enqueueing operations into a single method.
155+
/// See the [spec] for more information on the requirements that each operation must follow.
156+
///
157+
/// [spec]: https://tc39.es/ecma262/#sec-jobs
158+
fn enqueue_job(self: Rc<Self>, job: Job, context: &mut Context);
159+
160+
/// Runs all jobs in the executor.
161+
fn run_jobs(self: Rc<Self>, context: &mut Context) -> JsResult<()>;
162+
163+
/// Asynchronously runs all jobs in the executor.
164+
///
165+
/// By default forwards to [`JobExecutor::run_jobs`]. Implementors using async should override this
166+
/// with a proper algorithm to run jobs asynchronously.
167+
async fn run_jobs_async(self: Rc<Self>, context: &RefCell<&mut Context>) -> JsResult<()>
168+
where
169+
Self: Sized,
170+
{
171+
self.run_jobs(&mut context.borrow_mut())
172+
}
173+
}
174+
```
62175

63-
We also add support for span nodes in our parser and AST. Span nodes mark the start and
64-
end index in the source code for a specific token.
176+
As you can probably tell, we made a lot of changes on the `JobExecutor`:
65177

66-
As a result, this release of Boa supports error backtraces when an exception is thrown
67-
as seen below.
178+
TODO
68179

69-
![backtrace-example](./img/backtrace.gif)
180+
### Revamped `ModuleLoader`
70181

71-
This feature will greatly help with debugging errors when using Boa.
182+
TODO
72183

73-
### Specification updates
184+
### Built-ins updates
74185

75186
#### Set methods
76187

77-
This release adds support for the new set methods added in ES2025.
188+
This release adds support for the new set methods added in ECMAScript's 2025
189+
specification.
78190

79191
The new methods are:
80192

@@ -101,15 +213,18 @@ console.log(x[1]); // 42.125
101213

102214
#### Error.isError
103215

104-
This release adds support for `Error.isError`, which is accepted for ES2026.
216+
This release adds support for `Error.isError`, which will be introduced in
217+
ECMAScript's 2026 specification.
105218

106219
```javascript
107-
let check = Error.isError(new Error()); // returns true
220+
console.log(Error.isError(new Error())); // true
221+
console.log(Error.isError({ __proto__: Error.prototype })); // false
108222
```
109223

110224
#### Math.sumPrecise
111225

112-
This release adds support for `Math.sumPrecise`, which is accepted for ES2026.
226+
This release adds support for `Math.sumPrecise`, which will be introduced in
227+
ECMAScript's 2026 specification.
113228

114229
We've opted for using the new [`xsum`](https://crates.io/crates/xsum) summation algorithm
115230
for the underlying implementation.
@@ -119,6 +234,13 @@ let sum = Math.sumPrecise([1e20, 0.1, -1e20]);
119234
console.log(sum); // 0.1
120235
```
121236

237+
#### Atomics.waitAsync
238+
239+
TODO
240+
241+
242+
#### Array.fromAsync
243+
122244
## Boa Runtime
123245

124246
Work on Boa's runtime crate has continued with additional APIs added.
@@ -146,7 +268,7 @@ With this release, Boa's `JsValue` will use nan-boxing by default. The NaN boxin
146268
increased memory and runtime performance over the older enum.
147269

148270
As a note, the current implementation is not compatible with all platforms. While we hope
149-
to address this in the future. The legacy enum JsValue will be available via the `jsvalue-enum`
271+
to address this in the future, the legacy enum JsValue will be available via the `jsvalue-enum`
150272
feature flag.
151273

152274
Unfamiliar with NaN Boxing? We won't go over it in depth here, but we recommend

blog/boa-release-21/tapes/backtrace.tape

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,3 @@ Hide
2727
Type "rm yikes.js"
2828

2929
Enter
30-
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Output native-backtrace.gif
2+
3+
# Setup env
4+
Hide
5+
6+
Set TypingSpeed 50ms
7+
Set Theme "GruvboxDark"
8+
9+
# boa_cli needs to be installed for the tape to run.
10+
Require boa
11+
12+
Show
13+
14+
Type@100ms `cat <<EOF > native.js`
15+
Enter
16+
Type@100ms `Array.prototype.every.call([[10]], arr => arr.forEach(`
17+
Enter
18+
Type@100ms ` e => { throw value; }`
19+
Enter
20+
Type@100ms `));`
21+
Enter
22+
Type@100ms `EOF`
23+
Enter
24+
25+
Type@100ms "boa native.js"
26+
27+
Enter
28+
29+
Sleep 10s
30+
31+
Hide
32+
33+
Type "rm native.js"
34+
35+
Enter

0 commit comments

Comments
 (0)