# Spark · Malte Therkildsen

> Spark, a JavaScript engine I wrote from nothing, runs Ecma’s conformance suite, and a sample of it runs in the page. Open any test, change it and run it again.

Source: https://therkildsen.dev/writing/spark

[Writing](https://therkildsen.dev/writing)/Spark
W10SoftwareEngines from nothing

# Spark, a JavaScript engine held against Ecma’s tests

Spark is a JavaScript engine I wrote from an empty file, in plain JavaScript and without the browser’s own engine. It has its own parser, its own compiler, a machine that runs its own instructions, and its own built-ins. Ecma’s conformance suite for the language, , has  test files. Of the  that Spark runs,  pass. Below, a fixed sample of the suite runs in Spark in your browser, and any test in it can be opened, changed and run again.

PostedKind**Software**Series**Engines from nothing**

        Run the sample

      The sample loads when this part is on screen.

        Passed·

        Failed·

        Feature left out·

        Built-in not written·

        ·

```

```

            Front matter

              Run this test
              Put the text back

    The page draws one square for each test, sorted by path. Run the sample starts Spark in a Web Worker and runs every test it attempts, in each mode the test asks for. Gray squares use a feature Spark leaves out, and striped squares test a built-in Spark does not have, so the button skips both, as the full run does. Open any square to read the test, change it and run it again in a second worker.

## What a test is

      A  test is one file of JavaScript with a comment at the top written in YAML, its front matter. The front matter names the section of the specification the test is about, the helper files it includes, its flags and the language features it uses. The helper files `assert.js` and `sta.js` give every test `assert` and `Test262Error`, and a test fails by throwing.

      The front matter of a test that must throw has a `negative` entry with the phase and the error’s constructor. The phase is parse, resolution or runtime, so a test that expects a `SyntaxError` at parse fails if the same error comes later. Unless its flags name one mode, a test runs twice. It runs once as it is written, and once with `"use strict"` in front, because the two modes give some code a different meaning.

## How Spark runs one

      Each run gets a new Spark with a new global object and a new set of built-ins, so no earlier test has touched them. The helper files are compiled once and run again for each new global object. The test is given `$262`, the host object the suite’s `INTERPRETING.md` describes, with `evalScript`, `detachArrayBuffer` and `global`.

      A negative test passes only when Spark throws the named error in the named phase. For a parse test the error must come from Spark’s parser, and for a resolution test from its module loader. An async test passes when it calls `$DONE` without an error before Spark’s job queue is empty. Every run has a limit on steps and on time, so a test that loops without end fails instead of hanging the run.

## The count

      The command walks `test/language` and `test/built-ins` of a checkout of [tc39/test262](https://github.com/tc39/test262) at one commit, runs every test with three Node workers and counts each test once. A test passes when every mode passes.

```
$ node web/notes/spark/suite.mjs test262
test262 at 7ab7fafa0003: 47,547 test files
attempted 41,428: all of test/language and 53 directories of test/built-ins
  language   passed 22,763, failed  0, not run   963
  built-ins  passed 15,876, failed  2, not run 1,824
passed 38,639 of 38,641 run (74,319 runs in sloppy and strict mode), failed 2
not run for a feature Spark leaves out: 2,787, 35 features
    415  resizable-arraybuffer
    252  import-defer
    243  source-phase-imports
    230  regexp-modifiers
    201  explicit-resource-management
    187  regexp-v-flag
not attempted, built-ins Spark does not have: 6,119
  4,605  Temporal
    654  Iterator
    389  Atomics
    104  AsyncDisposableStack
failed:
  built-ins/TypedArrayConstructors/internals/DefineOwnProperty/BigInt/detached-buffer-throws-realm.js: TypeError: Spark runs one realm per test
  built-ins/TypedArrayConstructors/internals/DefineOwnProperty/detached-buffer-throws-realm.js: TypeError: Spark runs one realm per test
in 780 s with 3 workers
```

      The number Spark passes is counted against the tests it runs, and the output names the two groups that are left out of it. A test whose front matter lists a feature Spark does not have is not run. Most of those features came into the language after , the edition Spark was written against, and the rest are proposals that are not yet part of the language. The directories of `test/built-ins` for built-ins Spark does not have are not attempted, and Temporal is most of them. Counted against every test file in the suite, Spark passes  of .

## The two that fail

      Both failures are in `test/built-ins/TypedArrayConstructors`, and both call `$262.createRealm()` to make a typed array with a second global object. Spark gives each test one global object, and its `createRealm` throws a `TypeError` with the message `Spark runs one realm per test`. A test that needs a second global object is meant to list the feature `cross-realm` in its front matter, and the run leaves those tests out. These two do not list it, so they run and fail. Both are in the sample above, in red.

## The sample in the page

      The whole suite is too large to send to a browser, so the page runs a fixed sample. The command draws it with a seed from every test file, the ones Spark leaves out included, and adds every test that failed in the full run. Each test comes with the helper files it includes and the files it loads as modules. The command then runs the sample in Node the way the page’s worker runs it, and holds each result against the full run.

```
$ node web/notes/spark/sample.mjs test262 rows.jsonl
sample of test262 at 7ab7fafa0003: 1,002 tests, 1,000 drawn with seed 262 from 47,547 test files and 2 added that failed in the full run
17 harness files, 8 files loaded as modules, 1.95 MB
run 804 (1,548 runs in sloppy and strict mode): passed 802, failed 2, in 34.5 s
not run for a feature Spark leaves out: 64
not attempted, built-ins Spark does not have: 134
results that differ from the full run: 0
```

      In the page, one worker runs the sample a test at a time. A second worker runs the test you open, so a single test does not wait for the sample to finish. A gray or striped square is not run by the button, and Run it anyway runs it in Spark all the same.

## The limits of the count

      A pass means Spark gives the result the specification gives, in the cases the tests write down. The count does not measure speed. Spark is an interpreter written in JavaScript, and the time at the end of the command’s output is for three workers on one machine. The run covers `test/language` and `test/built-ins`. It leaves out `test/annexB`, the legacy additions web browsers keep, `test/intl402`, the tests for Intl, which Spark does not have, `test/staging` and `test/harness`.

      The source is in the portfolio’s repository. Spark is under `prototypes/source/p39/`, with `test262-harness.mjs` for running one test. Under `web/notes/spark/` are `suite.mjs` and `sample.mjs` for the commands, `run.js` for one test with its module files, and `worker.js` for the page’s worker. The tests are from [tc39/test262](https://github.com/tc39/test262), under the BSD license.

[NewerEvery post](https://therkildsen.dev/writing)[Older · W09Matching LibreOffice to the fifteenth digit](https://therkildsen.dev/writing/digits)
