Skip to content

Commit

Permalink
Experimental: add ahead-of-time compilation via weval and PBL.
Browse files Browse the repository at this point in the history
This PR pulls in my work to use "weval", the WebAssembly partial
evaluator, to perform ahead-of-time compilation of JavaScript using the
PBL interpreter we previously contributed to SpiderMonkey. This work has
been merged into the BA fork of SpiderMonkey in
bytecodealliance/gecko-dev#45,  bytecodealliance/gecko-dev#46,
bytecodealliance/gecko-dev#47, bytecodealliance/gecko-dev#48,
bytecodealliance/gecko-dev#51, bytecodealliance/gecko-dev#52,
bytecodealliance/gecko-dev#53, bytecodealliance/gecko-dev#54,
bytecodealliance/gecko-dev#55, and then integrated into StarlingMonkey
in bytecodealliance/StarlingMonkey#91.

The feature is off by default; it requires a `--enable-experimental-aot`
flag to be passed to `js-compute-runtime-cli.js`. This requires a
separate build of the engine Wasm module to be used when the flag is
passed.

This should still be considered experimental until it is tested more
widely. The PBL+weval combination passes all jit-tests and jstests in
SpiderMonkey, and all integration tests in StarlingMonkey; however, it
has not yet been widely tested in real-world scenarios.

Initial speedups we are seeing on Octane (CPU-intensive JS benchmarks)
are in the 3x-5x range. This is roughly equivalent to the speedup that a
native JS engine's "baseline JIT" compiler tier gets over its
interpreter, and it uses the same basic techniques -- compiling all
polymorphic operations (all basic JS operators) to inline-cache sites
that dispatch to stubs depending on types. Further speedups can be
obtained eventually by inlining stubs from warmed-up IC chains, but that
requires warmup.

Important to note is that this compilation approach is *fully
ahead-of-time*: it requires no profiling or observation or warmup of
user code, and compiles the JS directly to Wasm that does not do any
further codegen/JIT at runtime. Thus, it is suitable for the per-request
isolation model (new Wasm instance for each request, with no shared
state).
  • Loading branch information
cfallin committed Aug 1, 2024
1 parent f089616 commit 2f14390
Show file tree
Hide file tree
Showing 9 changed files with 530 additions and 224 deletions.
10 changes: 9 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
profile: [debug, release]
profile: [debug, release, weval]
steps:
- uses: actions/checkout@v3
with:
Expand All @@ -409,10 +409,18 @@ jobs:
- name: Build
if: ${{ matrix.profile == 'debug' }}
run: npm run build:starlingmonkey:debug
- name: Build
if: ${{ matrix.profile == 'weval' }}
run: npm run build:starlingmonkey:weval
- uses: actions/upload-artifact@v3
with:
name: starling-${{ matrix.profile }}
path: starling.wasm
- uses: actions/upload-artifact@v3
if: ${{ matrix.profile == 'weval' }}
with:
name: starling-${{ matrix.profile }}-ic-cache
path: starling-ics.wevalcache

starlingmonkey-run_wpt:
concurrency:
Expand Down
6 changes: 5 additions & 1 deletion js-compute-runtime-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { addSdkMetadataField } from "./src/addSdkMetadataField.js";

const {
enablePBL,
enableAOT,
aotCache,
enableExperimentalHighResolutionTimeMethods,
enableExperimentalTopLevelAwait,
starlingMonkey,
Expand Down Expand Up @@ -38,7 +40,9 @@ if (version) {
enableExperimentalHighResolutionTimeMethods,
enablePBL,
enableExperimentalTopLevelAwait,
starlingMonkey
starlingMonkey,
enableAOT,
aotCache,
);
await addSdkMetadataField(output, enablePBL, starlingMonkey);
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"build:debug": "DEBUG=true make -j8 -C runtime/js-compute-runtime && cp runtime/js-compute-runtime/js-compute-runtime.wasm .",
"build:starlingmonkey": "./runtime/fastly/build-release.sh",
"build:starlingmonkey:debug": "./runtime/fastly/build-debug.sh",
"build:starlingmonkey:weval": "./runtime/fastly/build-release-weval.sh",
"format-changelog": "node ci/format-changelog.js CHANGELOG.md"
},
"devDependencies": {
Expand All @@ -51,6 +52,7 @@
"dependencies": {
"@bytecodealliance/jco": "^1.3.1",
"@bytecodealliance/wizer": "^3.0.1",
"@cfallin/weval": "^0.2.7",
"acorn": "^8.12.1",
"acorn-walk": "^8.3.3",
"esbuild": "^0.23.0",
Expand Down
7 changes: 7 additions & 0 deletions runtime/fastly/build-release-weval.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
cd "$(dirname "$0")" || exit 1
RUNTIME_VERSION=$(npm pkg get version --json --prefix=../../ | jq -r)
HOST_API=$(realpath host-api) cmake -B build-release-weval -DCMAKE_BUILD_TYPE=Release -DENABLE_BUILTIN_WEB_FETCH=0 -DENABLE_BUILTIN_WEB_FETCH_FETCH_EVENT=0 -DRUNTIME_VERSION="\"$RUNTIME_VERSION\"" -DWEVAL=ON
cmake --build build-release-weval --parallel 8
mv build-release-weval/starling.wasm/starling.wasm ../../starling-weval.wasm
mv build-release-weval/starling.wasm/starling-ics.wevalcache ../../
92 changes: 64 additions & 28 deletions src/compileApplicationToWasm.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { rmSync } from "node:fs";
import { isFile } from "./isFile.js";
import { isFileOrDoesNotExist } from "./isFileOrDoesNotExist.js";
import wizer from "@bytecodealliance/wizer";
import weval from "@cfallin/weval";
import { precompile } from "./precompile.js";
import { enableTopLevelAwait } from "./enableTopLevelAwait.js";
import { bundle } from "./bundle.js";
Expand All @@ -21,7 +22,9 @@ export async function compileApplicationToWasm(
enableExperimentalHighResolutionTimeMethods = false,
enablePBL = false,
enableExperimentalTopLevelAwait = false,
starlingMonkey = false
starlingMonkey = false,
enableAOT = false,
aotCache = '',
) {
try {
if (!(await isFile(input))) {
Expand Down Expand Up @@ -121,35 +124,68 @@ export async function compileApplicationToWasm(
}

try {
let wizerProcess = spawnSync(
`"${wizer}"`,
[
"--inherit-env=true",
"--allow-wasi",
"--dir=.",
...starlingMonkey ? [`--dir=${dirname(wizerInput)}`] : [],
`--wasm-bulk-memory=true`,
"-r _start=wizer.resume",
`-o="${output}"`,
`"${wasmEngine}"`,
],
{
stdio: [null, process.stdout, process.stderr],
input: wizerInput,
shell: true,
encoding: "utf-8",
env: {
ENABLE_EXPERIMENTAL_HIGH_RESOLUTION_TIME_METHODS:
enableExperimentalHighResolutionTimeMethods ? "1" : "0",
ENABLE_PBL: enablePBL ? "1" : "0",
...process.env,
},
if (enableAOT) {
const wevalBin = await weval();

let wevalProcess = spawnSync(
`"${wevalBin}"`,
[
"weval",
...aotCache ? [`--cache-ro ${aotCache}`] : [],
"--dir .",
...starlingMonkey ? [`--dir ${dirname(wizerInput)}`] : [],
"-w",
`-i "${wasmEngine}"`,
`-o "${output}"`,
],
{
stdio: [null, process.stdout, process.stderr],
input: wizerInput,
shell: true,
encoding: "utf-8",
env: {
ENABLE_EXPERIMENTAL_HIGH_RESOLUTION_TIME_METHODS:
enableExperimentalHighResolutionTimeMethods ? "1" : "0",
ENABLE_PBL: enablePBL ? "1" : "0",
...process.env,
},
}
);
if (wevalProcess.status !== 0) {
throw new Error(`Weval initialization failure`);
}
);
if (wizerProcess.status !== 0) {
throw new Error(`Wizer initialization failure`);
process.exitCode = wevalProcess.status;
} else {
let wizerProcess = spawnSync(
`"${wizer}"`,
[
"--inherit-env=true",
"--allow-wasi",
"--dir=.",
...starlingMonkey ? [`--dir=${dirname(wizerInput)}`] : [],
`--wasm-bulk-memory=true`,
"-r _start=wizer.resume",
`-o="${output}"`,
`"${wasmEngine}"`,
],
{
stdio: [null, process.stdout, process.stderr],
input: wizerInput,
shell: true,
encoding: "utf-8",
env: {
ENABLE_EXPERIMENTAL_HIGH_RESOLUTION_TIME_METHODS:
enableExperimentalHighResolutionTimeMethods ? "1" : "0",
ENABLE_PBL: enablePBL ? "1" : "0",
...process.env,
},
}
);
if (wizerProcess.status !== 0) {
throw new Error(`Wizer initialization failure`);
}
process.exitCode = wizerProcess.status;
}
process.exitCode = wizerProcess.status;
} catch (error) {
console.error(
`Error: Failed to compile JavaScript to Wasm: `,
Expand Down
26 changes: 26 additions & 0 deletions src/parseInputs.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ export async function parseInputs(cliInputs) {
let enableExperimentalTopLevelAwait = false;
let starlingMonkey = true;
let enablePBL = false;
let enableAOT = false;
let customEngineSet = false;
let wasmEngine = join(__dirname, "../starling.wasm");
let aotCache = join(__dirname, "../starling-ics.wevalcache");
let customInputSet = false;
let input = join(process.cwd(), "bin/index.js");
let customOutputSet = false;
Expand All @@ -36,6 +38,10 @@ export async function parseInputs(cliInputs) {
enablePBL = true;
break;
}
case "--enable-experimental-aot": {
enableAOT = true;
break;
}
case "-V":
case "--version": {
return { version: true };
Expand Down Expand Up @@ -66,6 +72,14 @@ export async function parseInputs(cliInputs) {
}
break;
}
case "--aot-cache": {
if (isAbsolute(value)) {
aotCache = value;
} else {
aotCache = join(process.cwd(), value);
}
break;
}
default: {
// The reason this is not another `case` and is an `if` using `startsWith`
// is because previous versions of the CLI allowed an arbitrary amount of
Expand Down Expand Up @@ -108,10 +122,22 @@ export async function parseInputs(cliInputs) {
}
}
}

if (!starlingMonkey && enableAOT) {
// enableAOT forces StarlingMonkey.
console.log("AOT option is not compatible with pre-StarlingMonkey engine; please use StarlingMonkey.");
process.exit(1);
}
if (!customEngineSet && enableAOT) {
wasmEngine = join(__dirname, "../starling-weval.wasm");
}

return {
enableExperimentalHighResolutionTimeMethods,
enableExperimentalTopLevelAwait,
enablePBL,
enableAOT,
aotCache,
input,
output,
starlingMonkey,
Expand Down
1 change: 1 addition & 0 deletions src/printHelp.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ OPTIONS:
--engine-wasm <engine-wasm> The JS engine Wasm file path
--enable-experimental-high-resolution-time-methods Enable experimental high-resolution fastly.now() method
--enable-experimental-top-level-await Enable experimental top level await
--enable-experimental-aot Enable ahead-of-time compilation of JavaScript for faster execution
ARGS:
<input> The input JS script's file path [default: bin/index.js]
Expand Down
Loading

0 comments on commit 2f14390

Please sign in to comment.