Merge main

- Merge upstream main branch
- Partially merge https://github.com/WebKit/JetStream/pull/167/ to
  display sub-times

Change-Id: I9f30772ff2d6d068b09e43df3cb57da35b231ca2
Reviewed-on: https://chromium-review.googlesource.com/c/external/github.com/WebKit/JetStream/+/6911418
Reviewed-by: Victor Gomes <victorgomes@chromium.org>
This commit is contained in:
Camillo Bruni 2025-09-03 13:23:43 +02:00
parent 11ac3bea19
commit 97f348ec9d
250 changed files with 755371 additions and 533941 deletions

View file

@ -16,6 +16,7 @@ jobs:
env:
GITHUB_ACTIONS_OUTPUT: ""
strategy:
fail-fast: false
matrix:
browser: [chrome, firefox, jsc, safari, spidermonkey, v8]
steps:

5
.gitignore vendored
View file

@ -1,7 +1,10 @@
.DS_Store
.directory
/node_modules
node_modules
# Ignore auto-generated files by VS & VSCode.
/.vs/
/.vscode/
# v8.log is generated by the d8-shell if profiling is enabled
v8.log

View file

@ -38,8 +38,8 @@ class Benchmark {
romBinary;
async init() {
Module.wasmBinary = await getBinary(wasmBinary);
this.romBinary = await getBinary(romBinary);
Module.wasmBinary = await JetStream.getBinary(JetStream.preload.wasmBinary);
this.romBinary = await JetStream.getBinary(JetStream.preload.romBinary);
}
async runIteration() {

View file

@ -41,7 +41,6 @@ popd
echo "Copying files from 8bitbench/ into build/" | tee -a "$BUILD_LOG"
mkdir -p build/{lib/fast-text-encoding-1.0.3/,rust/pkg/,assets/}
cp 8bitbench/lib/fast-text-encoding-1.0.3/text.js build/lib/fast-text-encoding-1.0.3/
cp 8bitbench/rust/pkg/{emu_bench.js,emu_bench_bg.wasm} build/rust/pkg
cp 8bitbench/assets/tutorial/full_game.bin build/assets/program.bin

View file

@ -168,12 +168,3 @@ function bubbleSort(array, lessThan)
end--;
}
}
let currentTime;
if (this.performance && performance.now)
currentTime = function() { return performance.now() };
else if (this.preciseTime)
currentTime = function() { return preciseTime() * 1000; };
else
currentTime = function() { return +new Date(); };

View file

@ -30,14 +30,14 @@ class Benchmark {
let sources = [];
const files = [
[airBlob, {}]
, [basicBlob, {}]
, [inspectorBlob, {}]
, [babylonBlob, {sourceType: "module"}]
[JetStream.preload.airBlob, {}],
[JetStream.preload.basicBlob, {}],
[JetStream.preload.inspectorBlob, {}],
[JetStream.preload.babylonBlob, {sourceType: "module"}],
];
for (let [file, options] of files)
sources.push([file, await getString(file), options]);
sources.push([file, await JetStream.getString(file), options]);
this.sources = sources;
}

View file

@ -1,34 +0,0 @@
/*
* Copyright (C) 2016 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";
let currentTime;
if (this.performance && performance.now)
currentTime = function() { return performance.now() };
else if (this.preciseTime)
currentTime = function() { return preciseTime() * 1000; };
else
currentTime = function() { return +new Date(); };

View file

@ -2,8 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Excerpt from `build/run_wasm.js` to add own task queue implementation, since
// `setTimeout` and `queueMicrotask` are not always available in shells.
// Excerpt from `wasm_gc_benchmarks/tools/run_wasm.js` to add own task queue
// implementation, since `setTimeout` and `queueMicrotask` are not always
// available in shells.
// TODO: Now (2025-08-14) that all shells have `setTimeout` available, can we
// remove this? Talk to Dart2wasm folks.
function addTaskQueue(self) {
"use strict";
@ -62,8 +65,7 @@ function addTaskQueue(self) {
ms = Math.max(0, ms);
var id = timerIdCounter++;
// A callback can be scheduled at most once.
// (console.assert is only available on D8)
// if (isD8) console.assert(f.$timerId === undefined);
console.assert(f.$timerId === undefined);
f.$timerId = id;
timerIds[id] = f;
if (ms == 0 && !isNextTimerDue()) {
@ -262,8 +264,8 @@ class Benchmark {
// The generated JavaScript code from dart2wasm is an ES module, which we
// can only load with a dynamic import (since this file is not a module.)
Module.wasmBinary = await getBinary(wasmBinary);
this.dart2wasmJsModule = await dynamicImport(jsModule);
Module.wasmBinary = await JetStream.getBinary(JetStream.preload.wasmBinary);
this.dart2wasmJsModule = await JetStream.dynamicImport(JetStream.preload.jsModule);
}
async runIteration() {
@ -283,7 +285,7 @@ class Benchmark {
const framesToDraw = 100;
const initialFramesToSkip = 0;
const dartArgs = [
startTimeSinceEpochSeconds,
startTimeSinceEpochSeconds.toString(),
framesToDraw.toString(),
initialFramesToSkip.toString()
];

View file

@ -1,5 +1,5 @@
Built on 2025-03-06 09:52:17+01:00
Built on Thu Aug 14 04:47:37 PM CEST 2025
Cloning into 'wasm_gc_benchmarks'...
f80892d Update Dart SDK, switch wasm to -O2, recompile performance benchmarks
13951e1 Roll new version of flute and regenerate flute-based benchmarks
Copying files from wasm_gc_benchmarks/ into build/
Build success

View file

@ -7,19 +7,18 @@ rm -rf wasm_gc_benchmarks/
rm -rf build/
BUILD_LOG="$(realpath build.log)"
echo -e "Built on $(date --rfc-3339=seconds)" | tee "$BUILD_LOG"
echo -e "Built on $(date)" | tee "$BUILD_LOG"
git clone https://github.com/mkustermann/wasm_gc_benchmarks |& tee -a "$BUILD_LOG"
git clone https://github.com/mkustermann/wasm_gc_benchmarks 2>&1 | tee -a "$BUILD_LOG"
pushd wasm_gc_benchmarks/
git log -1 --oneline | tee -a "$BUILD_LOG"
popd
echo "Copying files from wasm_gc_benchmarks/ into build/" | tee -a "$BUILD_LOG"
mkdir -p build/ | tee -a "$BUILD_LOG"
# Generic Dart2wasm runner.
cp wasm_gc_benchmarks/tools/run_wasm.js build/ | tee -a "$BUILD_LOG"
# "Flute Complex" benchmark application.
cp wasm_gc_benchmarks/benchmarks-out/flute.dart2wasm.{mjs,wasm} build/ | tee -a "$BUILD_LOG"
# Two Flute benchmark applications: complex and todomvc
cp wasm_gc_benchmarks/benchmarks-out/flute.complex.dart2wasm.{mjs,wasm} build/ | tee -a "$BUILD_LOG"
cp wasm_gc_benchmarks/benchmarks-out/flute.todomvc.dart2wasm.{mjs,wasm} build/ | tee -a "$BUILD_LOG"
echo "Build success" | tee -a "$BUILD_LOG"

View file

@ -0,0 +1,358 @@
// Compiles a dart2wasm-generated main module from `source` which can then
// instantiatable via the `instantiate` method.
//
// `source` needs to be a `Response` object (or promise thereof) e.g. created
// via the `fetch()` JS API.
export async function compileStreaming(source) {
const builtins = {builtins: ['js-string']};
return new CompiledApp(
await WebAssembly.compileStreaming(source, builtins), builtins);
}
// Compiles a dart2wasm-generated wasm modules from `bytes` which is then
// instantiatable via the `instantiate` method.
export async function compile(bytes) {
const builtins = {builtins: ['js-string']};
return new CompiledApp(await WebAssembly.compile(bytes, builtins), builtins);
}
// DEPRECATED: Please use `compile` or `compileStreaming` to get a compiled app,
// use `instantiate` method to get an instantiated app and then call
// `invokeMain` to invoke the main function.
export async function instantiate(modulePromise, importObjectPromise) {
var moduleOrCompiledApp = await modulePromise;
if (!(moduleOrCompiledApp instanceof CompiledApp)) {
moduleOrCompiledApp = new CompiledApp(moduleOrCompiledApp);
}
const instantiatedApp = await moduleOrCompiledApp.instantiate(await importObjectPromise);
return instantiatedApp.instantiatedModule;
}
// DEPRECATED: Please use `compile` or `compileStreaming` to get a compiled app,
// use `instantiate` method to get an instantiated app and then call
// `invokeMain` to invoke the main function.
export const invoke = (moduleInstance, ...args) => {
moduleInstance.exports.$invokeMain(args);
}
class CompiledApp {
constructor(module, builtins) {
this.module = module;
this.builtins = builtins;
}
// The second argument is an options object containing:
// `loadDeferredWasm` is a JS function that takes a module name matching a
// wasm file produced by the dart2wasm compiler and returns the bytes to
// load the module. These bytes can be in either a format supported by
// `WebAssembly.compile` or `WebAssembly.compileStreaming`.
// `loadDynamicModule` is a JS function that takes two string names matching,
// in order, a wasm file produced by the dart2wasm compiler during dynamic
// module compilation and a corresponding js file produced by the same
// compilation. It should return a JS Array containing 2 elements. The first
// should be the bytes for the wasm module in a format supported by
// `WebAssembly.compile` or `WebAssembly.compileStreaming`. The second
// should be the result of using the JS 'import' API on the js file path.
async instantiate(additionalImports, {loadDeferredWasm, loadDynamicModule} = {}) {
let dartInstance;
// Prints to the console
function printToConsole(value) {
if (typeof dartPrint == "function") {
dartPrint(value);
return;
}
if (typeof console == "object" && typeof console.log != "undefined") {
console.log(value);
return;
}
if (typeof print == "function") {
print(value);
return;
}
throw "Unable to print message: " + value;
}
// A special symbol attached to functions that wrap Dart functions.
const jsWrappedDartFunctionSymbol = Symbol("JSWrappedDartFunction");
function finalizeWrapper(dartFunction, wrapped) {
wrapped.dartFunction = dartFunction;
wrapped[jsWrappedDartFunctionSymbol] = true;
return wrapped;
}
// Imports
const dart2wasm = {
_6: (o,s,v) => o[s] = v,
_39: x0 => x0.length,
_41: (x0,x1) => x0[x1],
_69: () => Symbol("jsBoxedDartObjectProperty"),
_70: (decoder, codeUnits) => decoder.decode(codeUnits),
_71: () => new TextDecoder("utf-8", {fatal: true}),
_72: () => new TextDecoder("utf-8", {fatal: false}),
_73: (s) => +s,
_74: Date.now,
_76: s => new Date(s * 1000).getTimezoneOffset() * 60,
_77: s => {
if (!/^\s*[+-]?(?:Infinity|NaN|(?:\.\d+|\d+(?:\.\d*)?)(?:[eE][+-]?\d+)?)\s*$/.test(s)) {
return NaN;
}
return parseFloat(s);
},
_78: () => {
let stackString = new Error().stack.toString();
let frames = stackString.split('\n');
let drop = 2;
if (frames[0] === 'Error') {
drop += 1;
}
return frames.slice(drop).join('\n');
},
_79: () => typeof dartUseDateNowForTicks !== "undefined",
_80: () => 1000 * performance.now(),
_81: () => Date.now(),
_84: () => new WeakMap(),
_85: (map, o) => map.get(o),
_86: (map, o, v) => map.set(o, v),
_99: s => JSON.stringify(s),
_100: s => printToConsole(s),
_101: (o, p, r) => o.replaceAll(p, () => r),
_103: Function.prototype.call.bind(String.prototype.toLowerCase),
_104: s => s.toUpperCase(),
_105: s => s.trim(),
_106: s => s.trimLeft(),
_107: s => s.trimRight(),
_108: (string, times) => string.repeat(times),
_109: Function.prototype.call.bind(String.prototype.indexOf),
_110: (s, p, i) => s.lastIndexOf(p, i),
_111: (string, token) => string.split(token),
_112: Object.is,
_113: o => o instanceof Array,
_118: a => a.pop(),
_119: (a, i) => a.splice(i, 1),
_120: (a, s) => a.join(s),
_121: (a, s, e) => a.slice(s, e),
_124: a => a.length,
_126: (a, i) => a[i],
_127: (a, i, v) => a[i] = v,
_130: (o, offsetInBytes, lengthInBytes) => {
var dst = new ArrayBuffer(lengthInBytes);
new Uint8Array(dst).set(new Uint8Array(o, offsetInBytes, lengthInBytes));
return new DataView(dst);
},
_132: o => o instanceof Uint8Array,
_133: (o, start, length) => new Uint8Array(o.buffer, o.byteOffset + start, length),
_135: (o, start, length) => new Int8Array(o.buffer, o.byteOffset + start, length),
_137: (o, start, length) => new Uint8ClampedArray(o.buffer, o.byteOffset + start, length),
_139: (o, start, length) => new Uint16Array(o.buffer, o.byteOffset + start, length),
_141: (o, start, length) => new Int16Array(o.buffer, o.byteOffset + start, length),
_143: (o, start, length) => new Uint32Array(o.buffer, o.byteOffset + start, length),
_145: (o, start, length) => new Int32Array(o.buffer, o.byteOffset + start, length),
_147: (o, start, length) => new BigInt64Array(o.buffer, o.byteOffset + start, length),
_149: (o, start, length) => new Float32Array(o.buffer, o.byteOffset + start, length),
_151: (o, start, length) => new Float64Array(o.buffer, o.byteOffset + start, length),
_152: (t, s) => t.set(s),
_154: (o) => new DataView(o.buffer, o.byteOffset, o.byteLength),
_156: o => o.buffer,
_157: o => o.byteOffset,
_158: Function.prototype.call.bind(Object.getOwnPropertyDescriptor(DataView.prototype, 'byteLength').get),
_159: (b, o) => new DataView(b, o),
_160: (b, o, l) => new DataView(b, o, l),
_161: Function.prototype.call.bind(DataView.prototype.getUint8),
_162: Function.prototype.call.bind(DataView.prototype.setUint8),
_163: Function.prototype.call.bind(DataView.prototype.getInt8),
_164: Function.prototype.call.bind(DataView.prototype.setInt8),
_165: Function.prototype.call.bind(DataView.prototype.getUint16),
_166: Function.prototype.call.bind(DataView.prototype.setUint16),
_167: Function.prototype.call.bind(DataView.prototype.getInt16),
_168: Function.prototype.call.bind(DataView.prototype.setInt16),
_169: Function.prototype.call.bind(DataView.prototype.getUint32),
_170: Function.prototype.call.bind(DataView.prototype.setUint32),
_171: Function.prototype.call.bind(DataView.prototype.getInt32),
_172: Function.prototype.call.bind(DataView.prototype.setInt32),
_175: Function.prototype.call.bind(DataView.prototype.getBigInt64),
_176: Function.prototype.call.bind(DataView.prototype.setBigInt64),
_177: Function.prototype.call.bind(DataView.prototype.getFloat32),
_178: Function.prototype.call.bind(DataView.prototype.setFloat32),
_179: Function.prototype.call.bind(DataView.prototype.getFloat64),
_180: Function.prototype.call.bind(DataView.prototype.setFloat64),
_193: (ms, c) =>
setTimeout(() => dartInstance.exports.$invokeCallback(c),ms),
_194: (handle) => clearTimeout(handle),
_197: (c) =>
queueMicrotask(() => dartInstance.exports.$invokeCallback(c)),
_232: (x0,x1) => x0.matchMedia(x1),
_233: (s, m) => {
try {
return new RegExp(s, m);
} catch (e) {
return String(e);
}
},
_234: (x0,x1) => x0.exec(x1),
_235: (x0,x1) => x0.test(x1),
_236: x0 => x0.pop(),
_238: o => o === undefined,
_240: o => typeof o === 'function' && o[jsWrappedDartFunctionSymbol] === true,
_243: o => o instanceof RegExp,
_244: (l, r) => l === r,
_245: o => o,
_246: o => o,
_247: o => o,
_249: o => o.length,
_251: (o, i) => o[i],
_252: f => f.dartFunction,
_253: () => ({}),
_263: o => String(o),
_265: o => {
if (o === undefined) return 1;
var type = typeof o;
if (type === 'boolean') return 2;
if (type === 'number') return 3;
if (type === 'string') return 4;
if (o instanceof Array) return 5;
if (ArrayBuffer.isView(o)) {
if (o instanceof Int8Array) return 6;
if (o instanceof Uint8Array) return 7;
if (o instanceof Uint8ClampedArray) return 8;
if (o instanceof Int16Array) return 9;
if (o instanceof Uint16Array) return 10;
if (o instanceof Int32Array) return 11;
if (o instanceof Uint32Array) return 12;
if (o instanceof Float32Array) return 13;
if (o instanceof Float64Array) return 14;
if (o instanceof DataView) return 15;
}
if (o instanceof ArrayBuffer) return 16;
// Feature check for `SharedArrayBuffer` before doing a type-check.
if (globalThis.SharedArrayBuffer !== undefined &&
o instanceof SharedArrayBuffer) {
return 17;
}
return 18;
},
_271: (jsArray, jsArrayOffset, wasmArray, wasmArrayOffset, length) => {
const setValue = dartInstance.exports.$wasmI8ArraySet;
for (let i = 0; i < length; i++) {
setValue(wasmArray, wasmArrayOffset + i, jsArray[jsArrayOffset + i]);
}
},
_275: (jsArray, jsArrayOffset, wasmArray, wasmArrayOffset, length) => {
const setValue = dartInstance.exports.$wasmI32ArraySet;
for (let i = 0; i < length; i++) {
setValue(wasmArray, wasmArrayOffset + i, jsArray[jsArrayOffset + i]);
}
},
_277: (jsArray, jsArrayOffset, wasmArray, wasmArrayOffset, length) => {
const setValue = dartInstance.exports.$wasmF32ArraySet;
for (let i = 0; i < length; i++) {
setValue(wasmArray, wasmArrayOffset + i, jsArray[jsArrayOffset + i]);
}
},
_279: (jsArray, jsArrayOffset, wasmArray, wasmArrayOffset, length) => {
const setValue = dartInstance.exports.$wasmF64ArraySet;
for (let i = 0; i < length; i++) {
setValue(wasmArray, wasmArrayOffset + i, jsArray[jsArrayOffset + i]);
}
},
_283: x0 => x0.index,
_285: x0 => x0.flags,
_286: x0 => x0.multiline,
_287: x0 => x0.ignoreCase,
_288: x0 => x0.unicode,
_289: x0 => x0.dotAll,
_290: (x0,x1) => { x0.lastIndex = x1 },
_295: x0 => x0.random(),
_298: () => globalThis.Math,
_299: Function.prototype.call.bind(Number.prototype.toString),
_300: Function.prototype.call.bind(BigInt.prototype.toString),
_301: Function.prototype.call.bind(Number.prototype.toString),
_302: (d, digits) => d.toFixed(digits),
_2062: () => globalThis.window,
_8706: x0 => x0.matches,
_12689: x0 => { globalThis.window.flutterCanvasKit = x0 },
};
const baseImports = {
dart2wasm: dart2wasm,
Math: Math,
Date: Date,
Object: Object,
Array: Array,
Reflect: Reflect,
S: new Proxy({}, { get(_, prop) { return prop; } }),
};
const jsStringPolyfill = {
"charCodeAt": (s, i) => s.charCodeAt(i),
"compare": (s1, s2) => {
if (s1 < s2) return -1;
if (s1 > s2) return 1;
return 0;
},
"concat": (s1, s2) => s1 + s2,
"equals": (s1, s2) => s1 === s2,
"fromCharCode": (i) => String.fromCharCode(i),
"length": (s) => s.length,
"substring": (s, a, b) => s.substring(a, b),
"fromCharCodeArray": (a, start, end) => {
if (end <= start) return '';
const read = dartInstance.exports.$wasmI16ArrayGet;
let result = '';
let index = start;
const chunkLength = Math.min(end - index, 500);
let array = new Array(chunkLength);
while (index < end) {
const newChunkLength = Math.min(end - index, 500);
for (let i = 0; i < newChunkLength; i++) {
array[i] = read(a, index++);
}
if (newChunkLength < chunkLength) {
array = array.slice(0, newChunkLength);
}
result += String.fromCharCode(...array);
}
return result;
},
"intoCharCodeArray": (s, a, start) => {
if (s === '') return 0;
const write = dartInstance.exports.$wasmI16ArraySet;
for (var i = 0; i < s.length; ++i) {
write(a, start++, s.charCodeAt(i));
}
return s.length;
},
"test": (s) => typeof s == "string",
};
dartInstance = await WebAssembly.instantiate(this.module, {
...baseImports,
...additionalImports,
"wasm:js-string": jsStringPolyfill,
});
return new InstantiatedApp(this, dartInstance);
}
}
class InstantiatedApp {
constructor(compiledApp, instantiatedModule) {
this.compiledApp = compiledApp;
this.instantiatedModule = instantiatedModule;
}
// Call the main function with the given arguments.
invokeMain(...args) {
this.instantiatedModule.exports.$invokeMain(args);
}
}

Binary file not shown.

View file

@ -0,0 +1,362 @@
// Compiles a dart2wasm-generated main module from `source` which can then
// instantiatable via the `instantiate` method.
//
// `source` needs to be a `Response` object (or promise thereof) e.g. created
// via the `fetch()` JS API.
export async function compileStreaming(source) {
const builtins = {builtins: ['js-string']};
return new CompiledApp(
await WebAssembly.compileStreaming(source, builtins), builtins);
}
// Compiles a dart2wasm-generated wasm modules from `bytes` which is then
// instantiatable via the `instantiate` method.
export async function compile(bytes) {
const builtins = {builtins: ['js-string']};
return new CompiledApp(await WebAssembly.compile(bytes, builtins), builtins);
}
// DEPRECATED: Please use `compile` or `compileStreaming` to get a compiled app,
// use `instantiate` method to get an instantiated app and then call
// `invokeMain` to invoke the main function.
export async function instantiate(modulePromise, importObjectPromise) {
var moduleOrCompiledApp = await modulePromise;
if (!(moduleOrCompiledApp instanceof CompiledApp)) {
moduleOrCompiledApp = new CompiledApp(moduleOrCompiledApp);
}
const instantiatedApp = await moduleOrCompiledApp.instantiate(await importObjectPromise);
return instantiatedApp.instantiatedModule;
}
// DEPRECATED: Please use `compile` or `compileStreaming` to get a compiled app,
// use `instantiate` method to get an instantiated app and then call
// `invokeMain` to invoke the main function.
export const invoke = (moduleInstance, ...args) => {
moduleInstance.exports.$invokeMain(args);
}
class CompiledApp {
constructor(module, builtins) {
this.module = module;
this.builtins = builtins;
}
// The second argument is an options object containing:
// `loadDeferredWasm` is a JS function that takes a module name matching a
// wasm file produced by the dart2wasm compiler and returns the bytes to
// load the module. These bytes can be in either a format supported by
// `WebAssembly.compile` or `WebAssembly.compileStreaming`.
// `loadDynamicModule` is a JS function that takes two string names matching,
// in order, a wasm file produced by the dart2wasm compiler during dynamic
// module compilation and a corresponding js file produced by the same
// compilation. It should return a JS Array containing 2 elements. The first
// should be the bytes for the wasm module in a format supported by
// `WebAssembly.compile` or `WebAssembly.compileStreaming`. The second
// should be the result of using the JS 'import' API on the js file path.
async instantiate(additionalImports, {loadDeferredWasm, loadDynamicModule} = {}) {
let dartInstance;
// Prints to the console
function printToConsole(value) {
if (typeof dartPrint == "function") {
dartPrint(value);
return;
}
if (typeof console == "object" && typeof console.log != "undefined") {
console.log(value);
return;
}
if (typeof print == "function") {
print(value);
return;
}
throw "Unable to print message: " + value;
}
// A special symbol attached to functions that wrap Dart functions.
const jsWrappedDartFunctionSymbol = Symbol("JSWrappedDartFunction");
function finalizeWrapper(dartFunction, wrapped) {
wrapped.dartFunction = dartFunction;
wrapped[jsWrappedDartFunctionSymbol] = true;
return wrapped;
}
// Imports
const dart2wasm = {
_6: (o,s,v) => o[s] = v,
_39: x0 => x0.length,
_41: (x0,x1) => x0[x1],
_69: () => Symbol("jsBoxedDartObjectProperty"),
_70: (decoder, codeUnits) => decoder.decode(codeUnits),
_71: () => new TextDecoder("utf-8", {fatal: true}),
_72: () => new TextDecoder("utf-8", {fatal: false}),
_73: (s) => +s,
_74: Date.now,
_76: s => new Date(s * 1000).getTimezoneOffset() * 60,
_77: s => {
if (!/^\s*[+-]?(?:Infinity|NaN|(?:\.\d+|\d+(?:\.\d*)?)(?:[eE][+-]?\d+)?)\s*$/.test(s)) {
return NaN;
}
return parseFloat(s);
},
_78: () => {
let stackString = new Error().stack.toString();
let frames = stackString.split('\n');
let drop = 2;
if (frames[0] === 'Error') {
drop += 1;
}
return frames.slice(drop).join('\n');
},
_79: () => typeof dartUseDateNowForTicks !== "undefined",
_80: () => 1000 * performance.now(),
_81: () => Date.now(),
_84: () => new WeakMap(),
_85: (map, o) => map.get(o),
_86: (map, o, v) => map.set(o, v),
_99: s => JSON.stringify(s),
_100: s => printToConsole(s),
_101: (o, p, r) => o.replaceAll(p, () => r),
_103: Function.prototype.call.bind(String.prototype.toLowerCase),
_104: s => s.toUpperCase(),
_105: s => s.trim(),
_106: s => s.trimLeft(),
_107: s => s.trimRight(),
_108: (string, times) => string.repeat(times),
_109: Function.prototype.call.bind(String.prototype.indexOf),
_110: (s, p, i) => s.lastIndexOf(p, i),
_111: (string, token) => string.split(token),
_112: Object.is,
_113: o => o instanceof Array,
_118: a => a.pop(),
_119: (a, i) => a.splice(i, 1),
_120: (a, s) => a.join(s),
_121: (a, s, e) => a.slice(s, e),
_124: a => a.length,
_126: (a, i) => a[i],
_127: (a, i, v) => a[i] = v,
_130: (o, offsetInBytes, lengthInBytes) => {
var dst = new ArrayBuffer(lengthInBytes);
new Uint8Array(dst).set(new Uint8Array(o, offsetInBytes, lengthInBytes));
return new DataView(dst);
},
_132: o => o instanceof Uint8Array,
_133: (o, start, length) => new Uint8Array(o.buffer, o.byteOffset + start, length),
_135: (o, start, length) => new Int8Array(o.buffer, o.byteOffset + start, length),
_137: (o, start, length) => new Uint8ClampedArray(o.buffer, o.byteOffset + start, length),
_139: (o, start, length) => new Uint16Array(o.buffer, o.byteOffset + start, length),
_141: (o, start, length) => new Int16Array(o.buffer, o.byteOffset + start, length),
_143: (o, start, length) => new Uint32Array(o.buffer, o.byteOffset + start, length),
_145: (o, start, length) => new Int32Array(o.buffer, o.byteOffset + start, length),
_147: (o, start, length) => new BigInt64Array(o.buffer, o.byteOffset + start, length),
_149: (o, start, length) => new Float32Array(o.buffer, o.byteOffset + start, length),
_151: (o, start, length) => new Float64Array(o.buffer, o.byteOffset + start, length),
_152: (t, s) => t.set(s),
_154: (o) => new DataView(o.buffer, o.byteOffset, o.byteLength),
_156: o => o.buffer,
_157: o => o.byteOffset,
_158: Function.prototype.call.bind(Object.getOwnPropertyDescriptor(DataView.prototype, 'byteLength').get),
_159: (b, o) => new DataView(b, o),
_160: (b, o, l) => new DataView(b, o, l),
_161: Function.prototype.call.bind(DataView.prototype.getUint8),
_162: Function.prototype.call.bind(DataView.prototype.setUint8),
_163: Function.prototype.call.bind(DataView.prototype.getInt8),
_164: Function.prototype.call.bind(DataView.prototype.setInt8),
_165: Function.prototype.call.bind(DataView.prototype.getUint16),
_166: Function.prototype.call.bind(DataView.prototype.setUint16),
_167: Function.prototype.call.bind(DataView.prototype.getInt16),
_168: Function.prototype.call.bind(DataView.prototype.setInt16),
_169: Function.prototype.call.bind(DataView.prototype.getUint32),
_170: Function.prototype.call.bind(DataView.prototype.setUint32),
_171: Function.prototype.call.bind(DataView.prototype.getInt32),
_172: Function.prototype.call.bind(DataView.prototype.setInt32),
_175: Function.prototype.call.bind(DataView.prototype.getBigInt64),
_176: Function.prototype.call.bind(DataView.prototype.setBigInt64),
_177: Function.prototype.call.bind(DataView.prototype.getFloat32),
_178: Function.prototype.call.bind(DataView.prototype.setFloat32),
_179: Function.prototype.call.bind(DataView.prototype.getFloat64),
_180: Function.prototype.call.bind(DataView.prototype.setFloat64),
_193: (ms, c) =>
setTimeout(() => dartInstance.exports.$invokeCallback(c),ms),
_194: (handle) => clearTimeout(handle),
_195: (ms, c) =>
setInterval(() => dartInstance.exports.$invokeCallback(c), ms),
_196: (handle) => clearInterval(handle),
_197: (c) =>
queueMicrotask(() => dartInstance.exports.$invokeCallback(c)),
_198: () => Date.now(),
_232: (x0,x1) => x0.matchMedia(x1),
_233: (s, m) => {
try {
return new RegExp(s, m);
} catch (e) {
return String(e);
}
},
_234: (x0,x1) => x0.exec(x1),
_235: (x0,x1) => x0.test(x1),
_236: x0 => x0.pop(),
_238: o => o === undefined,
_240: o => typeof o === 'function' && o[jsWrappedDartFunctionSymbol] === true,
_243: o => o instanceof RegExp,
_244: (l, r) => l === r,
_245: o => o,
_246: o => o,
_247: o => o,
_249: o => o.length,
_251: (o, i) => o[i],
_252: f => f.dartFunction,
_253: () => ({}),
_263: o => String(o),
_265: o => {
if (o === undefined) return 1;
var type = typeof o;
if (type === 'boolean') return 2;
if (type === 'number') return 3;
if (type === 'string') return 4;
if (o instanceof Array) return 5;
if (ArrayBuffer.isView(o)) {
if (o instanceof Int8Array) return 6;
if (o instanceof Uint8Array) return 7;
if (o instanceof Uint8ClampedArray) return 8;
if (o instanceof Int16Array) return 9;
if (o instanceof Uint16Array) return 10;
if (o instanceof Int32Array) return 11;
if (o instanceof Uint32Array) return 12;
if (o instanceof Float32Array) return 13;
if (o instanceof Float64Array) return 14;
if (o instanceof DataView) return 15;
}
if (o instanceof ArrayBuffer) return 16;
// Feature check for `SharedArrayBuffer` before doing a type-check.
if (globalThis.SharedArrayBuffer !== undefined &&
o instanceof SharedArrayBuffer) {
return 17;
}
return 18;
},
_271: (jsArray, jsArrayOffset, wasmArray, wasmArrayOffset, length) => {
const setValue = dartInstance.exports.$wasmI8ArraySet;
for (let i = 0; i < length; i++) {
setValue(wasmArray, wasmArrayOffset + i, jsArray[jsArrayOffset + i]);
}
},
_275: (jsArray, jsArrayOffset, wasmArray, wasmArrayOffset, length) => {
const setValue = dartInstance.exports.$wasmI32ArraySet;
for (let i = 0; i < length; i++) {
setValue(wasmArray, wasmArrayOffset + i, jsArray[jsArrayOffset + i]);
}
},
_277: (jsArray, jsArrayOffset, wasmArray, wasmArrayOffset, length) => {
const setValue = dartInstance.exports.$wasmF32ArraySet;
for (let i = 0; i < length; i++) {
setValue(wasmArray, wasmArrayOffset + i, jsArray[jsArrayOffset + i]);
}
},
_279: (jsArray, jsArrayOffset, wasmArray, wasmArrayOffset, length) => {
const setValue = dartInstance.exports.$wasmF64ArraySet;
for (let i = 0; i < length; i++) {
setValue(wasmArray, wasmArrayOffset + i, jsArray[jsArrayOffset + i]);
}
},
_283: x0 => x0.index,
_285: x0 => x0.flags,
_286: x0 => x0.multiline,
_287: x0 => x0.ignoreCase,
_288: x0 => x0.unicode,
_289: x0 => x0.dotAll,
_290: (x0,x1) => { x0.lastIndex = x1 },
_295: x0 => x0.random(),
_298: () => globalThis.Math,
_299: Function.prototype.call.bind(Number.prototype.toString),
_300: Function.prototype.call.bind(BigInt.prototype.toString),
_301: Function.prototype.call.bind(Number.prototype.toString),
_302: (d, digits) => d.toFixed(digits),
_2062: () => globalThis.window,
_8706: x0 => x0.matches,
_12689: x0 => { globalThis.window.flutterCanvasKit = x0 },
};
const baseImports = {
dart2wasm: dart2wasm,
Math: Math,
Date: Date,
Object: Object,
Array: Array,
Reflect: Reflect,
S: new Proxy({}, { get(_, prop) { return prop; } }),
};
const jsStringPolyfill = {
"charCodeAt": (s, i) => s.charCodeAt(i),
"compare": (s1, s2) => {
if (s1 < s2) return -1;
if (s1 > s2) return 1;
return 0;
},
"concat": (s1, s2) => s1 + s2,
"equals": (s1, s2) => s1 === s2,
"fromCharCode": (i) => String.fromCharCode(i),
"length": (s) => s.length,
"substring": (s, a, b) => s.substring(a, b),
"fromCharCodeArray": (a, start, end) => {
if (end <= start) return '';
const read = dartInstance.exports.$wasmI16ArrayGet;
let result = '';
let index = start;
const chunkLength = Math.min(end - index, 500);
let array = new Array(chunkLength);
while (index < end) {
const newChunkLength = Math.min(end - index, 500);
for (let i = 0; i < newChunkLength; i++) {
array[i] = read(a, index++);
}
if (newChunkLength < chunkLength) {
array = array.slice(0, newChunkLength);
}
result += String.fromCharCode(...array);
}
return result;
},
"intoCharCodeArray": (s, a, start) => {
if (s === '') return 0;
const write = dartInstance.exports.$wasmI16ArraySet;
for (var i = 0; i < s.length; ++i) {
write(a, start++, s.charCodeAt(i));
}
return s.length;
},
"test": (s) => typeof s == "string",
};
dartInstance = await WebAssembly.instantiate(this.module, {
...baseImports,
...additionalImports,
"wasm:js-string": jsStringPolyfill,
});
return new InstantiatedApp(this, dartInstance);
}
}
class InstantiatedApp {
constructor(compiledApp, instantiatedModule) {
this.compiledApp = compiledApp;
this.instantiatedModule = instantiatedModule;
}
// Call the main function with the given arguments.
invokeMain(...args) {
this.instantiatedModule.exports.$invokeMain(args);
}
}

Binary file not shown.

View file

@ -23,13 +23,38 @@
* THE POSSIBILITY OF SUCH DAMAGE.
*/
html {
:root {
--color-primary: #34AADC;
--color-secondary: #86D9FF;
--text-color-inverse: white;
--text-color-primary: black;
--text-color-secondary: #555555;
--text-color-tertiary: #444444;
--text-color-subtle: #6c6c71;
--text-color-very-subtle: #8E8E93;
--heading-color: #2C98D1;
--link-hover-color: #0086BF;
--button-color-primary: rgb(52, 170, 220);
--error-text-color: #d24a59;
--benchmark-heading-color: rgb(183, 183, 183);
--benchmark-error-text-color: #ff8686;
--benchmark-done-result-color: #4A4A4A;
--gap: 3rem;
--width: 200px;
}
html,
svg text {
font-family: "Helvetica Neue", Helvetica, Verdana, sans-serif;
font-size: 62.5%;
font-synthesis: none;
height: 100vh;
}
body {
display: flex;
flex-direction: column;
gap: var(--gap);
margin: 0;
font-size: 1.6rem;
font-weight: 400;
@ -39,32 +64,48 @@ body {
background-size: 100vw;
padding-bottom: 0px;
background-image: url('clouds.svg');
overflow-y: hidden;
height: 100%;
}
.overflow-scroll {
overflow-y: auto;
}
.overflow-visible {
overflow: visible;
}
::selection {
background-color: #34AADC;
color: white;
background-color: var(--color-primary);
color: var(--text-color-inverse);
}
main {
display: block;
display: flex;
flex-direction: column;
gap: var(--gap);
margin: 0 auto;
padding: 0 var(--gap);
max-width: 1180px;
margin: auto;
text-align: center;
flex: 1;
overflow: hidden;
}
main p {
margin: 0;
}
img {
-webkit-user-select: none;
-webkit-user-drag: none;
user-select: none;
}
.logo {
box-sizing: border-box;
width: 100%;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
perspective: 600;
}
@ -108,51 +149,59 @@ img {
text-align: center;
}
.summary + .summary {
padding-top: 5px;
margin-top: 5px;
}
.summary:empty {
display: none;
}
article, .summary {
article,
.summary {
max-width: 70rem;
margin: 0 auto 1rem;
margin: 0 auto 0 auto;
opacity: 0;
animation: fadein 0.5s ease-in-out forwards;
animation-delay: 200ms;
}
article {
display: flex;
flex-direction: column;
gap: var(--gap);
}
h1 {
color: black;
color: var(--text-color-primary);
text-align: center;
margin-bottom: 0;
}
h2, h3, h4, h5, h6 {
color: #2C98D1;
text-align: left;
h2,
h3,
h4,
h5,
h6 {
color: var(--heading-color);
text-align: left;
}
h4, h5, h6 {
h4,
h5,
h6 {
margin-bottom: 0;
}
p {
text-align: left;
color: #555555;
margin: 0 0 3rem 0;
color: var(--text-color-secondary);
}
h5, h6 {
h5,
h6 {
font-size: 1.6rem;
}
h6 {
color: #444444;
color: var(--text-color-tertiary);
}
dt {
@ -169,16 +218,12 @@ dd {
a:link,
a:visited {
color: #34AADC;
color: var(--color-primary);
}
a:hover,
a:active {
color: #0086BF;
}
#status {
margin: 2rem 0rem;
color: var(--link-hover-color);
}
#status label,
@ -187,16 +232,15 @@ a.button {
font-weight: 500;
text-decoration: none;
font-size: 2rem;
background-color: rgb(52,170,220);
background-image: linear-gradient(180deg, rgba(134,217,255,1) -80%, rgba(52,170,220,1) 100%);
color: rgb(255,255,255);
border: 1px solid rgb(52,170,220);
background-color: var(--button-color-primary);
background-image: linear-gradient(180deg, rgba(134, 217, 255, 1) -80%, rgb(52, 170, 220) 100%);
color: var(--text-color-inverse);
border: 1px solid var(--button-color-primary);
border-radius: 2px;
padding: 0.3rem 9rem 0.5rem;
padding: 0.3rem 0.3rem 0.5rem;
text-align: center;
width: var(--width);
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@ -224,12 +268,8 @@ a.button {
color: transparent;
background-image: linear-gradient(132deg, #96E5FF 0%, #96E5FF 2%, #86D9FF 42%, #8BDAFF 84%, #96E5FF 98%, #96E5FF 100%);
-webkit-background-clip: text;
background-size: 1200px 100%;
background-repeat: no-repeat;
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@ -238,8 +278,9 @@ a.button {
margin: 0 auto 1rem;
}
.error h2, .error p {
color: #d24a59;
.error h2,
.error p {
color: var(--error-text-color);
margin-bottom: 0;
text-align: center;
font-weight: 500;
@ -260,17 +301,22 @@ a.button {
}
#result-summary label {
color: #6c6c71;
color: var(--text-color-subtle);
}
#result-summary .score {
font-weight: bold;
font-size: 4rem;
line-height: 1;
color: #34AADC;
color: var(--color-primary);
font-weight: 500;
}
.benchmark .plot svg {
display: inline-block;
vertical-align: middle;
}
#result-summary .score .interval {
display: block;
font-weight: normal;
@ -280,57 +326,80 @@ a.button {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
gap: var(--gap);
margin: 0 calc(var(--gap) * -1) 0 calc(-var(--gap) * -1);
animation: fadein 500ms ease-out forwards;
opacity: 0;
overflow-y: auto;
flex: 1;
padding-bottom: var(--gap);
box-sizing: border-box;
}
.benchmark {
position: relative;
flex: 1;
max-width: 20%;
min-width: 200px;
min-width: var(--width);
text-align: left;
color: #8E8E93;
color: var(--text-color-very-subtle);
font-size: 1.6rem;
margin: 0 1.6rem 3rem 0;
scroll-margin-top: 20vh;
scroll-margin-bottom: 20vh;
}
.benchmark h4, .benchmark .result, .benchmark label {
.benchmark .result.detail {
display: none;
}
.details .benchmark .result.detail {
display: inline-block;
}
.benchmark h4,
.benchmark .result,
.benchmark label,
.benchmark .plot {
color: transparent;
background: linear-gradient(160deg, rgba(249,249,249,1) 0%, rgba(238,238,238,1) 100%);
background: linear-gradient(160deg, rgba(249, 249, 249, 1) 0%, rgba(238, 238, 238, 1) 100%);
border-radius: 3px;
}
.benchmark h3 {
color: rgb(183, 183, 183);
color: var(--benchmark-heading-color);
}
.benchmark-running h4, .benchmark-running .result, .benchmark-running label {
color: #86D9FF;
background-color: #86D9FF;
.benchmark-running h4,
.benchmark-running .result,
.benchmark-running label,
.benchmark-running .plot {
color: var(--color-secondary);
background-color: var(--color-secondary);
background-image: none;
}
.benchmark-done h3, .benchmark-done h4, .benchmark-done .result, .benchmark-done label {
.benchmark-done h3,
.benchmark-done h4,
.benchmark-done .result,
.benchmark-done label,
.benchmark-done .plot {
background-color: transparent;
background-image: none;
-webkit-touch-callout: revert;
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
.benchmark-error h4, .benchmark-error .result, .benchmark-error label {
color: #ff8686;
background-color: #ff8686;
.benchmark-error h4,
.benchmark-error .result,
.benchmark-error label,
.benchmark-error .plot {
color: var(--benchmark-error-text-color);
background-color: var(--benchmark-error-text-color);
background-image: none;
}
.benchmark-error h3 {
color: #ff8686;
color: var(--benchmark-error-text-color);
}
.benchmark h3 {
@ -342,11 +411,37 @@ a.button {
.benchmark-running h3 {
background-color: transparent;
background-image: none;
color: #34AADC;
color: var(--color-primary);
}
.benchmark-done h3 {
color: #6c6c71;
color: var(--text-color-subtle);
}
.benchmark a.info {
display: inline-block;
width: 1.5rem;
height: 1.5rem;
border-radius: 100%;
background-color: var(--benchmark-heading-color);
color: var(--text-color-inverse) !important;
user-select: none;
text-align: center;
vertical-align: middle;
font-style: italic;
font-weight: bold;
font-family: 'Times New Roman', Times, serif;
line-height: 1.6rem;
margin-top: -0.2rem;
}
.benchmark-running a.info {
background-color: var(--color-primary);
}
.benchmark-done a.info {
background-color: var(--text-color-subtle);
}
.benchmark h3 a,
@ -359,7 +454,7 @@ a.button {
}
.benchmark-done h3 a:hover {
color: #34AADC;
color: var(--color-primary);
text-decoration: underline;
}
@ -371,7 +466,7 @@ a.button {
}
.benchmark-done h4 {
color: #34AADC;
color: var(--color-primary);
background-color: none;
}
@ -388,8 +483,15 @@ a.button {
font-weight: bold;
}
#result-summary .benchmark {
text-align: center;
max-width: 100%;
margin: 0;
margin-left: 1.6rem;
}
.benchmark-done .result {
color: #4A4A4A;
color: var(--benchmark-done-result-color);
}
.benchmark label {
@ -397,14 +499,27 @@ a.button {
}
.benchmark-done label {
color: #8E8E93;
color: var(--text-color-very-subtle);
font-weight: 400;
}
dt:target {
background-color: var(--color-secondary);
color: var(--text-color-inverse);
padding-left: 10px;
border-radius: 5px;
}
.plot svg circle {
fill: var(--color-primary);
opacity: 0.8;
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
@ -412,10 +527,11 @@ a.button {
@keyframes scaledown {
from {
transform: scale(1.3,1.3);
transform: scale(1.3, 1.3);
}
to {
transform: scale(1,1);
transform: scale(1, 1);
}
}
@ -423,6 +539,7 @@ a.button {
0% {
background-position: -1200px center;
}
100% {
background-position: 100vw center;
}
@ -433,6 +550,7 @@ a.button {
opacity: 0;
transform: rotateY(-85deg) translateZ(200px);
}
to {
opacity: 1;
transform: rotateY(0deg) translateZ(0px);
@ -451,14 +569,4 @@ a.button {
#jetstreams {
background-size: 200%;
}
article, .summary {
padding-top: 10rem;
margin: 0 1rem;
}
a.button {
padding: 0.3rem 6rem 0.5rem;
}
}
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

File diff suppressed because it is too large Load diff

1
Kotlin-compose/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/compose-multiplatform/

18
Kotlin-compose/README.md Normal file
View file

@ -0,0 +1,18 @@
# Kotlin/Wasm Compose Multiplatform Benchmark
Citing https://github.com/JetBrains/compose-multiplatform:
> [Compose Multiplatform](https://jb.gg/cmp) is a declarative framework for sharing UIs across multiple platforms with Kotlin.
[...]
Compose for Web is based on [Kotlin/Wasm](https://kotl.in/wasm), the newest target for Kotlin Multiplatform projects.
It allows Kotlin developers to run their code in the browser with all the benefits that WebAssembly has to offer, such as good and predictable performance for your applications.
## Build Instructions
See or run `build.sh`.
See `build.log` for the last build time, used sources, and toolchain versions.
## Running in JS shells
To run the unmodified upstream benchmark, without the JetStream driver, see the
upstream repo, specifically https://github.com/JetBrains/compose-multiplatform/blob/master/benchmarks/multiplatform/README.md

158
Kotlin-compose/benchmark.js Normal file
View file

@ -0,0 +1,158 @@
// Copyright 2025 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Excerpt from `polyfills.mjs` from the upstream Kotlin compose-multiplatform
// benchmark directory, with minor changes for JetStream.
globalThis.window ??= globalThis;
globalThis.navigator ??= {};
if (!globalThis.navigator.languages) {
globalThis.navigator.languages = ['en-US', 'en'];
globalThis.navigator.userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
globalThis.navigator.platform = "MacIntel";
}
// Compose reads `window.isSecureContext` in its Clipboard feature:
globalThis.isSecureContext = false;
// Disable explicit GC (it wouldn't work in browsers anyway).
globalThis.gc = () => {
// DEBUG
// console.log("gc()");
}
class URL {
href;
constructor(url, base) {
// DEBUG
// console.log('URL', url, base);
this.href = url;
}
}
globalThis.URL = URL;
// We always polyfill `fetch` and `instantiateStreaming` for consistency between
// engine shells and browsers and to avoid introducing network latency into the
// first iteration / instantiation measurement.
// The downside is that this doesn't test streaming Wasm instantiation, which we
// are willing to accept.
let preload = { /* Initialized in init() below due to async. */ };
const originalFetch = globalThis.fetch ?? function(url) {
throw new Error("no fetch available");
}
globalThis.fetch = async function(url) {
// DEBUG
// console.log('fetch', url);
// Redirect some paths to cached/preloaded resources.
if (preload[url]) {
return {
ok: true,
status: 200,
arrayBuffer() { return preload[url]; },
async blob() {
return {
size: preload[url].byteLength,
async arrayBuffer() { return preload[url]; }
}
},
};
}
// This should only be called in the browser, where fetch() is available.
return originalFetch(url);
};
globalThis.WebAssembly.instantiateStreaming = async function(m,i) {
// DEBUG
// console.log('instantiateStreaming',m,i);
return WebAssembly.instantiate((await m).arrayBuffer(),i);
};
// Provide `setTimeout` for Kotlin coroutines.
const originalSetTimeout = setTimeout;
globalThis.setTimeout = function(f, delayMs) {
// DEBUG
// console.log('setTimeout', f, t);
// Deep in the Compose UI framework, one task is scheduled every 16ms, see
// https://github.com/JetBrains/compose-multiplatform-core/blob/a52f2981b9bc7cdba1d1fbe71654c4be448ebea7/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectManager.kt#L138
// and
// https://github.com/JetBrains/compose-multiplatform-core/blob/a52f2981b9bc7cdba1d1fbe71654c4be448ebea7/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnLayoutRectChangedModifier.kt#L56
// We don't want to delay work in the Wall-time based measurement in JetStream,
// but executing this immediately (without delay) produces redundant work that
// is not realistic for a full-browser Kotlin/multiplatform application either,
// according to Kotlin/JetBrains folks.
// Hence the early return for 16ms delays.
if (delayMs === 16) return;
if (delayMs !== 0) {
throw new Error('Unexpected delay for setTimeout polyfill: ' + delayMs);
}
originalSetTimeout(f);
}
// Don't automatically run the main function on instantiation.
globalThis.skipFunMain = true;
// Prevent this from being detected as a shell environment, so that we use the
// same code paths as in the browser.
// See `compose-benchmarks-benchmarks.uninstantiated.mjs`.
delete globalThis.d8;
delete globalThis.inIon;
delete globalThis.jscOptions;
class Benchmark {
skikoInstantiate;
mainInstantiate;
wasmInstanceExports;
async init() {
// DEBUG
// console.log("init");
preload = {
'skiko.wasm': await JetStream.getBinary(JetStream.preload.skikoWasmBinary),
'./compose-benchmarks-benchmarks.wasm': await JetStream.getBinary(JetStream.preload.composeWasmBinary),
'./composeResources/compose_benchmarks.benchmarks.generated.resources/drawable/compose-multiplatform.png': await JetStream.getBinary(JetStream.preload.inputImageCompose),
'./composeResources/compose_benchmarks.benchmarks.generated.resources/drawable/example1_cat.jpg': await JetStream.getBinary(JetStream.preload.inputImageCat),
'./composeResources/compose_benchmarks.benchmarks.generated.resources/files/example1_compose-community-primary.png': await JetStream.getBinary(JetStream.preload.inputImageComposeCommunity),
'./composeResources/compose_benchmarks.benchmarks.generated.resources/font/jetbrainsmono_italic.ttf': await JetStream.getBinary(JetStream.preload.inputFontItalic),
'./composeResources/compose_benchmarks.benchmarks.generated.resources/font/jetbrainsmono_regular.ttf': await JetStream.getBinary(JetStream.preload.inputFontRegular),
};
// We patched `skiko.mjs` to not immediately instantiate the `skiko.wasm`
// module, so that we can move the dynamic JS import here but measure
// WebAssembly compilation and instantiation as part of the first iteration.
this.skikoInstantiate = (await JetStream.dynamicImport(JetStream.preload.skikoJsModule)).default;
this.mainInstantiate = (await JetStream.dynamicImport(JetStream.preload.composeJsModule)).instantiate;
}
async runIteration() {
// DEBUG
// console.log("runIteration");
// Compile once in the first iteration.
if (!this.wasmInstanceExports) {
const skikoExports = (await this.skikoInstantiate()).wasmExports;
this.wasmInstanceExports = (await this.mainInstantiate({ './skiko.mjs': skikoExports })).exports;
}
// We render/animate/process fewer frames than in the upstream benchmark,
// since we run multiple iterations in JetStream (to measure first, worst,
// and average runtime) and don't want the overall workload to take too long.
const frameCountFactor = 5;
// The factors for the subitems are chosen to make them take the same order
// of magnitude in terms of Wall time.
await this.wasmInstanceExports.customLaunch("AnimatedVisibility", 100 * frameCountFactor);
await this.wasmInstanceExports.customLaunch("LazyGrid", 1 * frameCountFactor);
await this.wasmInstanceExports.customLaunch("LazyGrid-ItemLaunchedEffect", 1 * frameCountFactor);
// The `SmoothScroll` variants of the LazyGrid workload are much faster.
await this.wasmInstanceExports.customLaunch("LazyGrid-SmoothScroll", 5 * frameCountFactor);
await this.wasmInstanceExports.customLaunch("LazyGrid-SmoothScroll-ItemLaunchedEffect", 5 * frameCountFactor);
// This is quite GC-heavy, is this realistic for Kotlin/compose applications?
await this.wasmInstanceExports.customLaunch("VisualEffects", 1 * frameCountFactor);
await this.wasmInstanceExports.customLaunch("MultipleComponents-NoVectorGraphics", 10 * frameCountFactor);
}
}

5
Kotlin-compose/build.log Normal file
View file

@ -0,0 +1,5 @@
Built on 2025-08-14 12:27:42+02:00
Cloning into 'compose-multiplatform'...
84dad4d3f6 Use custom skiko (0.9.4.3) to fix the FinalizationRegistry API usage for web targets
Copying generated files into build/
Build success

46
Kotlin-compose/build.sh Executable file
View file

@ -0,0 +1,46 @@
#!/bin/bash
set -eo pipefail
# Cleanup old files.
rm -rf build/
rm -rf compose-multiplatform/
BUILD_LOG="$(realpath build.log)"
echo -e "Built on $(date --rfc-3339=seconds)" | tee "$BUILD_LOG"
# Build the benchmark from source.
# FIXME: Use main branch and remove hotfix patch below, once
# https://youtrack.jetbrains.com/issue/SKIKO-1040 is resolved upstream.
# See https://github.com/WebKit/JetStream/pull/84#discussion_r2252418900.
git clone -b ok/jetstream3_hotfix https://github.com/JetBrains/compose-multiplatform.git |& tee -a "$BUILD_LOG"
pushd compose-multiplatform/
git log -1 --oneline | tee -a "$BUILD_LOG"
# Do not read `isD8` in the main function which decides whether to run eagerly.
# Instead, just patch that out statically.
# TODO: Upstream that fix to the compose-multiplatform repo.
git apply ../empty-main-function.patch | tee -a "$BUILD_LOG"
# FIXME: Use stable 2.3 Kotlin/Wasm toolchain, once available around Sep 2025.
git apply ../use-beta-toolchain.patch | tee -a "$BUILD_LOG"
pushd benchmarks/multiplatform
./gradlew :benchmarks:wasmJsProductionExecutableCompileSync
# For building polyfills and JavaScript launcher to run in d8 (which inspires the benchmark.js launcher here):
# ./gradlew :benchmarks:buildD8Distribution
BUILD_SRC_DIR="compose-multiplatform/benchmarks/multiplatform/build/wasm/packages/compose-benchmarks-benchmarks/kotlin"
popd
popd
echo "Copying generated files into build/" | tee -a "$BUILD_LOG"
mkdir -p build/ | tee -a "$BUILD_LOG"
cp $BUILD_SRC_DIR/compose-benchmarks-benchmarks.{wasm,uninstantiated.mjs} build/ | tee -a "$BUILD_LOG"
git apply hook-print.patch | tee -a "$BUILD_LOG"
# FIXME: Remove once the JSC fixes around JSTag have landed in Safari, see
# https://github.com/WebKit/JetStream/pull/84#issuecomment-3164672425.
git apply jstag-workaround.patch | tee -a "$BUILD_LOG"
cp $BUILD_SRC_DIR/skiko.{wasm,mjs} build/ | tee -a "$BUILD_LOG"
git apply skiko-disable-instantiate.patch | tee -a "$BUILD_LOG"
cp $BUILD_SRC_DIR/composeResources/compose_benchmarks.benchmarks.generated.resources/drawable/example1_cat.jpg build/ | tee -a "$BUILD_LOG"
cp $BUILD_SRC_DIR/composeResources/compose_benchmarks.benchmarks.generated.resources/drawable/compose-multiplatform.png build/ | tee -a "$BUILD_LOG"
cp $BUILD_SRC_DIR/composeResources/compose_benchmarks.benchmarks.generated.resources/files/example1_compose-community-primary.png build/ | tee -a "$BUILD_LOG"
cp $BUILD_SRC_DIR/composeResources/compose_benchmarks.benchmarks.generated.resources/font/jetbrainsmono_*.ttf build/ | tee -a "$BUILD_LOG"
echo "Build success" | tee -a "$BUILD_LOG"

View file

@ -0,0 +1,446 @@
export async function instantiate(imports={}, runInitializer=true) {
const cachedJsObjects = new WeakMap();
// ref must be non-null
function getCachedJsObject(ref, ifNotCached) {
if (typeof ref !== 'object' && typeof ref !== 'function') return ifNotCached;
const cached = cachedJsObjects.get(ref);
if (cached !== void 0) return cached;
cachedJsObjects.set(ref, ifNotCached);
return ifNotCached;
}
const _ref_Li9za2lrby5tanM_ = imports['./skiko.mjs'];
const _ref_QGpzLWpvZGEvY29yZQ_ = imports['@js-joda/core'];
const js_code = {
'kotlin.createJsError' : (message, cause) => new Error(message, { cause }),
'kotlin.wasm.internal.stringLength' : (x) => x.length,
'kotlin.wasm.internal.jsExportStringToWasm' : (src, srcOffset, srcLength, dstAddr) => {
const mem16 = new Uint16Array(wasmExports.memory.buffer, dstAddr, srcLength);
let arrayIndex = 0;
let srcIndex = srcOffset;
while (arrayIndex < srcLength) {
mem16.set([src.charCodeAt(srcIndex)], arrayIndex);
srcIndex++;
arrayIndex++;
}
},
'kotlin.wasm.internal.importStringFromWasm' : (address, length, prefix) => {
const mem16 = new Uint16Array(wasmExports.memory.buffer, address, length);
const str = String.fromCharCode.apply(null, mem16);
return (prefix == null) ? str : prefix + str;
},
'kotlin.wasm.internal.externrefToBoolean' : (ref) => Boolean(ref),
'kotlin.wasm.internal.intToExternref' : (x) => x,
'kotlin.wasm.internal.getJsEmptyString' : () => '',
'kotlin.wasm.internal.externrefToString' : (ref) => String(ref),
'kotlin.wasm.internal.externrefEquals' : (lhs, rhs) => lhs === rhs,
'kotlin.wasm.internal.externrefHashCode' :
(() => {
const dataView = new DataView(new ArrayBuffer(8));
function numberHashCode(obj) {
if ((obj | 0) === obj) {
return obj | 0;
} else {
dataView.setFloat64(0, obj, true);
return (dataView.getInt32(0, true) * 31 | 0) + dataView.getInt32(4, true) | 0;
}
}
const hashCodes = new WeakMap();
function getObjectHashCode(obj) {
const res = hashCodes.get(obj);
if (res === undefined) {
const POW_2_32 = 4294967296;
const hash = (Math.random() * POW_2_32) | 0;
hashCodes.set(obj, hash);
return hash;
}
return res;
}
function getStringHashCode(str) {
var hash = 0;
for (var i = 0; i < str.length; i++) {
var code = str.charCodeAt(i);
hash = (hash * 31 + code) | 0;
}
return hash;
}
return (obj) => {
if (obj == null) {
return 0;
}
switch (typeof obj) {
case "object":
case "function":
return getObjectHashCode(obj);
case "number":
return numberHashCode(obj);
case "boolean":
return obj ? 1231 : 1237;
default:
return getStringHashCode(String(obj));
}
}
})(),
'kotlin.wasm.internal.isNullish' : (ref) => ref == null,
'kotlin.wasm.internal.externrefToInt' : (ref) => Number(ref),
'kotlin.wasm.internal.getJsTrue' : () => true,
'kotlin.wasm.internal.getJsFalse' : () => false,
'kotlin.wasm.internal.newJsArray' : () => [],
'kotlin.wasm.internal.jsArrayPush' : (array, element) => { array.push(element); },
'kotlin.wasm.internal.getCachedJsObject_$external_fun' : (p0, p1) => getCachedJsObject(p0, p1),
'kotlin.js.jsCatch' : (f) => {
let result = null;
try {
f();
} catch (e) {
result = e;
}
return result;
},
'kotlin.js.__convertKotlinClosureToJsClosure_(()->Unit)' : (f) => getCachedJsObject(f, () => wasmExports['__callFunction_(()->Unit)'](f, )),
'kotlin.js.jsThrow' : (e) => { throw e; },
'kotlin.io.printlnImpl' : (message) => print(message),
'kotlin.io.printError' : (error) => printErr(error),
'kotlin.js.jsArrayGet' : (array, index) => array[index],
'kotlin.js.jsArraySet' : (array, index, value) => { array[index] = value },
'kotlin.js.JsArray_$external_fun' : () => new Array(),
'kotlin.js.length_$external_prop_getter' : (_this) => _this.length,
'kotlin.js.JsArray_$external_class_instanceof' : (x) => x instanceof Array,
'kotlin.js.JsArray_$external_class_get' : () => Array,
'kotlin.js.stackPlaceHolder_js_code' : () => (''),
'kotlin.js.message_$external_prop_getter' : (_this) => _this.message,
'kotlin.js.name_$external_prop_setter' : (_this, v) => _this.name = v,
'kotlin.js.stack_$external_prop_getter' : (_this) => _this.stack,
'kotlin.js.kotlinException_$external_prop_getter' : (_this) => _this.kotlinException,
'kotlin.js.kotlinException_$external_prop_setter' : (_this, v) => _this.kotlinException = v,
'kotlin.js.JsError_$external_class_instanceof' : (x) => x instanceof Error,
'kotlin.js.JsString_$external_class_instanceof' : (x) => typeof x === 'string',
'kotlin.js.JsString_$external_class_get' : () => JsString,
'kotlin.js.Promise_$external_fun' : (p0) => new Promise(p0),
'kotlin.js.__callJsClosure_((Js?)->Unit)' : (f, p0) => f(p0),
'kotlin.js.__callJsClosure_((Js)->Unit)' : (f, p0) => f(p0),
'kotlin.js.__convertKotlinClosureToJsClosure_((((Js?)->Unit),((Js)->Unit))->Unit)' : (f) => getCachedJsObject(f, (p0, p1) => wasmExports['__callFunction_((((Js?)->Unit),((Js)->Unit))->Unit)'](f, p0, p1)),
'kotlin.js.then_$external_fun' : (_this, p0) => _this.then(p0),
'kotlin.js.__convertKotlinClosureToJsClosure_((Js?)->Js?)' : (f) => getCachedJsObject(f, (p0) => wasmExports['__callFunction_((Js?)->Js?)'](f, p0)),
'kotlin.js.then_$external_fun_1' : (_this, p0, p1) => _this.then(p0, p1),
'kotlin.js.__convertKotlinClosureToJsClosure_((Js)->Js?)' : (f) => getCachedJsObject(f, (p0) => wasmExports['__callFunction_((Js)->Js?)'](f, p0)),
'kotlin.js.catch_$external_fun' : (_this, p0) => _this.catch(p0),
'kotlin.js.Promise_$external_class_instanceof' : (x) => x instanceof Promise,
'kotlin.js.Promise_$external_class_get' : () => Promise,
'kotlin.random.initialSeed' : () => ((Math.random() * Math.pow(2, 32)) | 0),
'kotlin.wasm.internal.getJsClassName' : (jsKlass) => jsKlass.name,
'kotlin.wasm.internal.instanceOf' : (ref, jsKlass) => ref instanceof jsKlass,
'kotlin.wasm.internal.getConstructor' : (obj) => obj.constructor,
'kotlin.time.tryGetPerformance' : () => typeof globalThis !== 'undefined' && typeof globalThis.performance !== 'undefined' ? globalThis.performance : null,
'kotlin.time.getPerformanceNow' : (performance) => performance.now(),
'kotlin.time.dateNow' : () => Date.now(),
'kotlinx.browser.window_$external_prop_getter' : () => window,
'kotlinx.browser.document_$external_prop_getter' : () => document,
'org.w3c.dom.length_$external_prop_getter' : (_this) => _this.length,
'org.khronos.webgl.getMethodImplForInt8Array' : (obj, index) => { return obj[index]; },
'org.khronos.webgl.getMethodImplForUint8Array' : (obj, index) => { return obj[index]; },
'org.khronos.webgl.slice_$external_fun' : (_this, p0, p1, isDefault0) => _this.slice(p0, isDefault0 ? undefined : p1, ),
'org.khronos.webgl.Int8Array_$external_fun' : (p0, p1, p2, isDefault0, isDefault1) => new Int8Array(p0, isDefault0 ? undefined : p1, isDefault1 ? undefined : p2, ),
'org.khronos.webgl.length_$external_prop_getter' : (_this) => _this.length,
'org.khronos.webgl.Uint8Array_$external_fun' : (p0, p1, p2, isDefault0, isDefault1) => new Uint8Array(p0, isDefault0 ? undefined : p1, isDefault1 ? undefined : p2, ),
'org.khronos.webgl.length_$external_prop_getter_1' : (_this) => _this.length,
'org.khronos.webgl.buffer_$external_prop_getter' : (_this) => _this.buffer,
'org.khronos.webgl.byteOffset_$external_prop_getter' : (_this) => _this.byteOffset,
'org.khronos.webgl.byteLength_$external_prop_getter' : (_this) => _this.byteLength,
'org.w3c.dom.clipboard.clipboardData_$external_prop_getter' : (_this) => _this.clipboardData,
'org.w3c.dom.clipboard.ClipboardEvent_$external_class_instanceof' : (x) => x instanceof ClipboardEvent,
'org.w3c.dom.events.__convertKotlinClosureToJsClosure_((Js)->Unit)' : (f) => getCachedJsObject(f, (p0) => wasmExports['__callFunction_((Js)->Unit)'](f, p0)),
'org.w3c.dom.events.addEventListener_$external_fun' : (_this, p0, p1) => _this.addEventListener(p0, p1),
'org.w3c.dom.events.addEventListener_$external_fun_1' : (_this, p0, p1) => _this.addEventListener(p0, p1),
'org.w3c.dom.events.removeEventListener_$external_fun' : (_this, p0, p1) => _this.removeEventListener(p0, p1),
'org.w3c.dom.events.removeEventListener_$external_fun_1' : (_this, p0, p1) => _this.removeEventListener(p0, p1),
'org.w3c.dom.events.type_$external_prop_getter' : (_this) => _this.type,
'org.w3c.dom.events.preventDefault_$external_fun' : (_this, ) => _this.preventDefault(),
'org.w3c.dom.events.Event_$external_class_instanceof' : (x) => x instanceof Event,
'org.w3c.dom.events.Event_$external_class_get' : () => Event,
'org.w3c.dom.events.key_$external_prop_getter' : (_this) => _this.key,
'org.w3c.dom.events.KeyboardEvent_$external_class_instanceof' : (x) => x instanceof KeyboardEvent,
'org.w3c.dom.location_$external_prop_getter' : (_this) => _this.location,
'org.w3c.dom.navigator_$external_prop_getter' : (_this) => _this.navigator,
'org.w3c.dom.devicePixelRatio_$external_prop_getter' : (_this) => _this.devicePixelRatio,
'org.w3c.dom.matchMedia_$external_fun' : (_this, p0) => _this.matchMedia(p0),
'org.w3c.dom.matches_$external_prop_getter' : (_this) => _this.matches,
'org.w3c.dom.protocol_$external_prop_getter' : (_this) => _this.protocol,
'org.w3c.dom.hostname_$external_prop_getter' : (_this) => _this.hostname,
'org.w3c.dom.getData_$external_fun' : (_this, p0) => _this.getData(p0),
'org.w3c.dom.setData_$external_fun' : (_this, p0, p1) => _this.setData(p0, p1),
'org.w3c.dom.language_$external_prop_getter' : (_this) => _this.language,
'org.w3c.dom.clearTimeout_$external_fun' : (_this, p0, isDefault0) => _this.clearTimeout(isDefault0 ? undefined : p0, ),
'org.w3c.dom.fetch_$external_fun' : (_this, p0, p1, isDefault0) => _this.fetch(p0, isDefault0 ? undefined : p1, ),
'org.w3c.dom.documentElement_$external_prop_getter' : (_this) => _this.documentElement,
'org.w3c.dom.namespaceURI_$external_prop_getter' : (_this) => _this.namespaceURI,
'org.w3c.dom.localName_$external_prop_getter' : (_this) => _this.localName,
'org.w3c.dom.getAttribute_$external_fun' : (_this, p0) => _this.getAttribute(p0),
'org.w3c.dom.getAttributeNS_$external_fun' : (_this, p0, p1) => _this.getAttributeNS(p0, p1),
'org.w3c.dom.Element_$external_class_instanceof' : (x) => x instanceof Element,
'org.w3c.dom.data_$external_prop_getter' : (_this) => _this.data,
'org.w3c.dom.nodeName_$external_prop_getter' : (_this) => _this.nodeName,
'org.w3c.dom.childNodes_$external_prop_getter' : (_this) => _this.childNodes,
'org.w3c.dom.lookupPrefix_$external_fun' : (_this, p0) => _this.lookupPrefix(p0),
'org.w3c.dom.item_$external_fun' : (_this, p0) => _this.item(p0),
'org.w3c.dom.binaryType_$external_prop_setter' : (_this, v) => _this.binaryType = v,
'org.w3c.dom.close_$external_fun' : (_this, p0, p1, isDefault0, isDefault1) => _this.close(isDefault0 ? undefined : p0, isDefault1 ? undefined : p1, ),
'org.w3c.dom.send_$external_fun' : (_this, p0) => _this.send(p0),
'org.w3c.dom.send_$external_fun_1' : (_this, p0) => _this.send(p0),
'org.w3c.dom.Companion_$external_object_getInstance' : () => ({}),
'org.w3c.dom.code_$external_prop_getter' : (_this) => _this.code,
'org.w3c.dom.reason_$external_prop_getter' : (_this) => _this.reason,
'org.w3c.dom.parsing.DOMParser_$external_fun' : () => new DOMParser(),
'org.w3c.dom.parsing.parseFromString_$external_fun' : (_this, p0, p1) => _this.parseFromString(p0, p1),
'org.w3c.fetch.status_$external_prop_getter' : (_this) => _this.status,
'org.w3c.fetch.ok_$external_prop_getter' : (_this) => _this.ok,
'org.w3c.fetch.statusText_$external_prop_getter' : (_this) => _this.statusText,
'org.w3c.fetch.headers_$external_prop_getter' : (_this) => _this.headers,
'org.w3c.fetch.body_$external_prop_getter' : (_this) => _this.body,
'org.w3c.fetch.blob_$external_fun' : (_this, ) => _this.blob(),
'org.w3c.fetch.get_$external_fun' : (_this, p0) => _this.get(p0),
'org.w3c.performance.now_$external_fun' : (_this, ) => _this.now(),
'org.w3c.performance.performance_$external_prop_getter' : (_this) => _this.performance,
'org.w3c.xhr.XMLHttpRequest_$external_fun' : () => new XMLHttpRequest(),
'org.w3c.xhr.status_$external_prop_getter' : (_this) => _this.status,
'org.w3c.xhr.open_$external_fun' : (_this, p0, p1, p2, p3, p4, isDefault0, isDefault1) => _this.open(p0, p1, p2, isDefault0 ? undefined : p3, isDefault1 ? undefined : p4, ),
'org.w3c.xhr.send_$external_fun' : (_this, ) => _this.send(),
'org.w3c.xhr.overrideMimeType_$external_fun' : (_this, p0) => _this.overrideMimeType(p0),
'kotlinx.coroutines.tryGetProcess' : () => (typeof(process) !== 'undefined' && typeof(process.nextTick) === 'function') ? process : null,
'kotlinx.coroutines.tryGetWindow' : () => (typeof(window) !== 'undefined' && window != null && typeof(window.addEventListener) === 'function') ? window : null,
'kotlinx.coroutines.nextTick_$external_fun' : (_this, p0) => _this.nextTick(p0),
'kotlinx.coroutines.error_$external_fun' : (_this, p0) => _this.error(p0),
'kotlinx.coroutines.console_$external_prop_getter' : () => console,
'kotlinx.coroutines.createScheduleMessagePoster' : (process) => () => Promise.resolve(0).then(process),
'kotlinx.coroutines.__callJsClosure_(()->Unit)' : (f, ) => f(),
'kotlinx.coroutines.createRescheduleMessagePoster' : (window) => () => window.postMessage('dispatchCoroutine', '*'),
'kotlinx.coroutines.subscribeToWindowMessages' : (window, process) => {
const handler = (event) => {
if (event.source == window && event.data == 'dispatchCoroutine') {
event.stopPropagation();
process();
}
}
window.addEventListener('message', handler, true);
},
'kotlinx.coroutines.setTimeout' : (window, handler, timeout) => window.setTimeout(handler, timeout),
'kotlinx.coroutines.clearTimeout' : (handle) => { if (typeof clearTimeout !== 'undefined') clearTimeout(handle); },
'kotlinx.coroutines.setTimeout_$external_fun' : (p0, p1) => setTimeout(p0, p1),
'kotlinx.coroutines.promiseSetDeferred' : (promise, deferred) => promise.deferred = deferred,
'androidx.compose.runtime.internal.weakMap_js_code' : () => (new WeakMap()),
'androidx.compose.runtime.internal.set_$external_fun' : (_this, p0, p1) => _this.set(p0, p1),
'androidx.compose.runtime.internal.get_$external_fun' : (_this, p0) => _this.get(p0),
'org.jetbrains.skiko.w3c.language_$external_prop_getter' : (_this) => _this.language,
'org.jetbrains.skiko.w3c.userAgent_$external_prop_getter' : (_this) => _this.userAgent,
'org.jetbrains.skiko.w3c.navigator_$external_prop_getter' : (_this) => _this.navigator,
'org.jetbrains.skiko.w3c.performance_$external_prop_getter' : (_this) => _this.performance,
'org.jetbrains.skiko.w3c.open_$external_fun' : (_this, p0, p1) => _this.open(p0, p1),
'org.jetbrains.skiko.w3c.window_$external_object_getInstance' : () => window,
'org.jetbrains.skiko.w3c.now_$external_fun' : (_this, ) => _this.now(),
'org.jetbrains.skia.impl.FinalizationRegistry_$external_fun' : (p0) => new FinalizationRegistry(p0),
'org.jetbrains.skia.impl.register_$external_fun' : (_this, p0, p1, p2) => _this.register(p0, p1, p2),
'org.jetbrains.skia.impl.unregister_$external_fun' : (_this, p0) => _this.unregister(p0),
'org.jetbrains.skia.impl._releaseLocalCallbackScope_$external_fun' : () => _ref_Li9za2lrby5tanM_._releaseLocalCallbackScope(),
'org.jetbrains.skiko.getNavigatorInfo' : () => navigator.userAgentData ? navigator.userAgentData.platform : navigator.platform,
'androidx.compose.ui.text.intl.getUserPreferredLanguagesAsArray' : () => window.navigator.languages,
'androidx.compose.ui.text.intl.parseLanguageTagToIntlLocale' : (languageTag) => new Intl.Locale(languageTag),
'androidx.compose.ui.text.intl._language_$external_prop_getter' : (_this) => _this.language,
'androidx.compose.ui.text.intl._region_$external_prop_getter' : (_this) => _this.region,
'androidx.compose.ui.text.intl._baseName_$external_prop_getter' : (_this) => _this.baseName,
'androidx.compose.ui.internal.weakMap_js_code' : () => (new WeakMap()),
'androidx.compose.ui.internal.set_$external_fun' : (_this, p0, p1) => _this.set(p0, p1),
'androidx.compose.ui.internal.get_$external_fun' : (_this, p0) => _this.get(p0),
'androidx.compose.ui.platform.isSecureContext' : () => window.isSecureContext,
'androidx.compose.ui.platform.invalidClipboardItems' : () => [],
'androidx.compose.ui.platform.createClipboardItemWithPlainText' : (text) => [new ClipboardItem({'text/plain': new Blob([text], { type: 'text/plain' })})],
'androidx.compose.ui.platform.getW3CClipboard' : () => window.navigator.clipboard,
'androidx.compose.ui.platform.emptyClipboardItems' : () => [new ClipboardItem({'text/plain': new Blob([''], { type: 'text/plain' })})],
'androidx.compose.ui.platform.read_$external_fun' : (_this, ) => _this.read(),
'androidx.compose.ui.platform.write_$external_fun' : (_this, p0) => _this.write(p0),
'androidx.compose.ui.platform.W3CTemporaryClipboard_$external_class_instanceof' : (x) => x instanceof Clipboard,
'androidx.compose.ui.platform.W3CTemporaryClipboard_$external_class_get' : () => Clipboard,
'androidx.compose.ui.platform.types_$external_prop_getter' : (_this) => _this.types,
'androidx.compose.ui.platform.getType_$external_fun' : (_this, p0) => _this.getType(p0),
'androidx.compose.foundation.internal.doesJsArrayContainValue' : (jsArray, value) => jsArray.includes(value),
'androidx.compose.foundation.internal.getTextFromBlob' : (blob) => blob.text(),
'androidx.compose.foundation.text.EventListener' : (handler) => (event) => { handler(event) },
'androidx.compose.material.internal.weakMap_js_code' : () => (new WeakMap()),
'androidx.compose.material.internal.set_$external_fun' : (_this, p0, p1) => _this.set(p0, p1),
'androidx.compose.material.internal.get_$external_fun' : (_this, p0) => _this.get(p0),
'org.jetbrains.compose.resources.Locale_$external_fun' : (p0) => new Intl.Locale(p0),
'org.jetbrains.compose.resources.language_$external_prop_getter' : (_this) => _this.language,
'org.jetbrains.compose.resources.region_$external_prop_getter' : (_this) => _this.region,
'org.jetbrains.compose.resources.jsExportBlobAsArrayBuffer' : (blob) => blob.arrayBuffer(),
'org.jetbrains.compose.resources.jsExportInt8ArrayToWasm' : (src, size, dstAddr) => {
const mem8 = new Int8Array(wasmExports.memory.buffer, dstAddr, size);
mem8.set(src);
}
,
'org.jetbrains.compose.resources.requestResponseAsByteArray' : (req) => {
var text = req.responseText;
var int8Arr = new Int8Array(text.length);
for (var i = 0; i < text.length; i++) {
int8Arr[i] = text.charCodeAt(i) & 0xFF;
}
return int8Arr;
},
'org.jetbrains.compose.resources.isInTestEnvironment' : () => window.composeResourcesTesting == true,
'io.ktor.utils.io.js.decode' : (decoder) => { try { return decoder.decode() } catch(e) { return null } },
'io.ktor.utils.io.js.decode_1' : (decoder, buffer) => { try { return decoder.decode(buffer) } catch(e) { return null } },
'io.ktor.utils.io.js.decodeStream' : (decoder, buffer) => { try { return decoder.decode(buffer, { stream: true }) } catch(e) { return null } },
'io.ktor.utils.io.js.tryCreateTextDecoder' : (encoding, fatal) => { try { return new TextDecoder(encoding, { fatal: fatal }) } catch(e) { return null } },
'io.ktor.utils.io.js.TextEncoder_$external_fun' : () => new TextEncoder(),
'io.ktor.utils.io.js.encode_$external_fun' : (_this, p0) => _this.encode(p0),
'io.ktor.utils.io.js.toJsArrayImpl' : (x) => new Int8Array(x),
'io.ktor.util.hasNodeApi' : () =>
(typeof process !== 'undefined'
&& process.versions != null
&& process.versions.node != null) ||
(typeof window !== 'undefined'
&& typeof window.process !== 'undefined'
&& window.process.versions != null
&& window.process.versions.node != null)
,
'io.ktor.util.logging.getKtorLogLevel' : () => process.env.KTOR_LOG_LEVEL,
'io.ktor.util.logging.debug_$external_fun' : (_this, p0) => _this.debug(p0),
'io.ktor.util.logging.console_$external_prop_getter' : () => console,
'io.ktor.util.date.Date_$external_fun' : () => new Date(),
'io.ktor.util.date.Date_$external_fun_1' : (p0) => new Date(p0),
'io.ktor.util.date.getTime_$external_fun' : (_this, ) => _this.getTime(),
'io.ktor.util.date.getUTCDate_$external_fun' : (_this, ) => _this.getUTCDate(),
'io.ktor.util.date.getUTCDay_$external_fun' : (_this, ) => _this.getUTCDay(),
'io.ktor.util.date.getUTCFullYear_$external_fun' : (_this, ) => _this.getUTCFullYear(),
'io.ktor.util.date.getUTCHours_$external_fun' : (_this, ) => _this.getUTCHours(),
'io.ktor.util.date.getUTCMinutes_$external_fun' : (_this, ) => _this.getUTCMinutes(),
'io.ktor.util.date.getUTCMonth_$external_fun' : (_this, ) => _this.getUTCMonth(),
'io.ktor.util.date.getUTCSeconds_$external_fun' : (_this, ) => _this.getUTCSeconds(),
'io.ktor.http.locationOrigin' : () => function() {
var origin = ""
if (typeof window !== 'undefined') {
origin = window.location.origin
} else {
origin = self.location.origin
}
return origin && origin != "null" ? origin : "http://localhost"
}(),
'io.ktor.client.engine.js.createBrowserWebSocket' : (urlString_capturingHack, protocols) => new WebSocket(urlString_capturingHack, protocols),
'io.ktor.client.engine.js.createWebSocketNodeJs' : (socketCtor, urlString_capturingHack, headers_capturingHack, protocols) => new socketCtor(urlString_capturingHack, protocols, { headers: headers_capturingHack }),
'io.ktor.client.engine.js.getKeys' : (headers) => Array.from(headers.keys()),
'io.ktor.client.engine.js.eventAsString' : (event) => JSON.stringify(event, ['message', 'target', 'type', 'isTrusted']),
'io.ktor.client.engine.js.compatibility.abortControllerCtorBrowser' : () => AbortController,
'io.ktor.client.engine.js.node.bodyOn' : (body, type, handler) => body.on(type, handler),
'io.ktor.client.engine.js.node.bodyOn_1' : (body, type, handler) => body.on(type, handler),
'io.ktor.client.engine.js.node.pause_$external_fun' : (_this, ) => _this.pause(),
'io.ktor.client.engine.js.node.resume_$external_fun' : (_this, ) => _this.resume(),
'io.ktor.client.engine.js.node.destroy_$external_fun' : (_this, p0) => _this.destroy(p0),
'io.ktor.client.fetch.signal_$external_prop_setter' : (_this, v) => _this.signal = v,
'io.ktor.client.fetch.signal_$external_prop_getter' : (_this) => _this.signal,
'io.ktor.client.fetch.abort_$external_fun' : (_this, ) => _this.abort(),
'io.ktor.client.fetch.fetch_$external_fun' : (p0, p1, isDefault0) => fetch(p0, isDefault0 ? undefined : p1, ),
'io.ktor.client.fetch.getReader_$external_fun' : (_this, ) => _this.getReader(),
'io.ktor.client.fetch.cancel_$external_fun' : (_this, p0, isDefault0) => _this.cancel(isDefault0 ? undefined : p0, ),
'io.ktor.client.fetch.read_$external_fun' : (_this, ) => _this.read(),
'io.ktor.client.fetch.done_$external_prop_getter' : (_this) => _this.done,
'io.ktor.client.fetch.value_$external_prop_getter' : (_this) => _this.value,
'io.ktor.client.plugins.websocket.tryGetEventDataAsString' : (data) => typeof(data) === 'string' ? data : null,
'io.ktor.client.plugins.websocket.tryGetEventDataAsArrayBuffer' : (data) => data instanceof ArrayBuffer ? data : null,
'io.ktor.client.utils.makeJsObject' : () => { return {}; },
'io.ktor.client.utils.makeRequire' : (name) => require(name),
'io.ktor.client.utils.makeJsCall' : (func, arg) => func.apply(null, arg),
'io.ktor.client.utils.makeJsNew' : (ctor) => new ctor(),
'io.ktor.client.utils.setObjectField' : (obj, name, value) => obj[name]=value,
'io.ktor.client.utils.toJsArrayImpl' : (x) => new Uint8Array(x),
'runGC' : () => { (typeof gc === 'function')? gc() : console.log('Manual GC is not available. Ensure that the browser was started with the appropriate flags.') }
}
// Placed here to give access to it from externals (js_code)
let wasmInstance;
let require;
let wasmExports;
const isNodeJs = (typeof process !== 'undefined') && (process.release.name === 'node');
const isDeno = !isNodeJs && (typeof Deno !== 'undefined')
const isStandaloneJsVM =
!isDeno && !isNodeJs && (
typeof d8 !== 'undefined' // V8
|| typeof inIon !== 'undefined' // SpiderMonkey
|| typeof jscOptions !== 'undefined' // JavaScriptCore
);
const isBrowser = !isNodeJs && !isDeno && !isStandaloneJsVM && (typeof window !== 'undefined' || typeof self !== 'undefined');
if (!isNodeJs && !isDeno && !isStandaloneJsVM && !isBrowser) {
throw "Supported JS engine not detected";
}
const wasmFilePath = './compose-benchmarks-benchmarks.wasm';
const wasmTag = new WebAssembly.Tag({ parameters: ['externref'] });
const importObject = {
js_code,
intrinsics: {
tag: wasmTag
},
'./skiko.mjs': imports['./skiko.mjs'],
};
try {
if (isNodeJs) {
const module = await import(/* webpackIgnore: true */'node:module');
const importMeta = import.meta;
require = module.default.createRequire(importMeta.url);
const fs = require('fs');
const url = require('url');
const filepath = import.meta.resolve(wasmFilePath);
const wasmBuffer = fs.readFileSync(url.fileURLToPath(filepath));
const wasmModule = new WebAssembly.Module(wasmBuffer);
wasmInstance = new WebAssembly.Instance(wasmModule, importObject);
}
if (isDeno) {
const path = await import(/* webpackIgnore: true */'https://deno.land/std/path/mod.ts');
const binary = Deno.readFileSync(path.fromFileUrl(import.meta.resolve(wasmFilePath)));
const module = await WebAssembly.compile(binary);
wasmInstance = await WebAssembly.instantiate(module, importObject);
}
if (isStandaloneJsVM) {
const wasmBuffer = read(wasmFilePath, 'binary');
const wasmModule = new WebAssembly.Module(wasmBuffer);
wasmInstance = new WebAssembly.Instance(wasmModule, importObject);
}
if (isBrowser) {
wasmInstance = (await WebAssembly.instantiateStreaming(fetch(new URL('./compose-benchmarks-benchmarks.wasm',import.meta.url).href), importObject)).instance;
}
} catch (e) {
if (e instanceof WebAssembly.CompileError) {
let text = `Please make sure that your runtime environment supports the latest version of Wasm GC and Exception-Handling proposals.
For more information, see https://kotl.in/wasm-help
`;
if (isBrowser) {
console.error(text);
} else {
const t = "\n" + text;
if (typeof console !== "undefined" && console.log !== void 0)
console.log(t);
else
print(t);
}
}
throw e;
}
wasmExports = wasmInstance.exports;
if (runInitializer) {
wasmExports._initialize();
}
return { instance: wasmInstance, exports: wasmExports };
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

BIN
Kotlin-compose/build/skiko.wasm Executable file

Binary file not shown.

View file

@ -0,0 +1,39 @@
diff --git a/benchmarks/multiplatform/benchmarks/src/wasmJsMain/kotlin/main.wasmJs.kt b/benchmarks/multiplatform/benchmarks/src/wasmJsMain/kotlin/main.wasmJs.kt
index d796d88975..f2c9e48eda 100644
--- a/benchmarks/multiplatform/benchmarks/src/wasmJsMain/kotlin/main.wasmJs.kt
+++ b/benchmarks/multiplatform/benchmarks/src/wasmJsMain/kotlin/main.wasmJs.kt
@@ -8,11 +8,6 @@ import kotlin.js.Promise
val jsOne = 1.toJsNumber()
fun main(args: Array<String>) {
- if (isD8env().toBoolean()) {
- mainD8(args)
- } else {
- mainBrowser()
- }
}
fun mainBrowser() {
@@ -37,15 +32,6 @@ fun mainBrowser() {
}
}
-
-// Currently, the initialization can't be adjusted to avoid calling the fun main, but
-// we don't want use the default fun main, because Jetstream3 requires running the workloads separately / independently of each other.
-// Also, they require that a benchmark completes before the function exists, which is not possible with if they just call fun main.
-// Therefore, they'll rely on fun customLaunch, which returns a Promise (can be awaited for).
-fun mainD8(args: Array<String>) {
- println("mainD8 is intentionally doing nothing. Read the comments in main.wasmJs.kt")
-}
-
private val basicConfigForD8 = Config(
// Using only SIMPLE mode, because VSYNC_EMULATION calls delay(...),
// which is implemented via setTimeout on web targets.
@@ -85,6 +71,3 @@ fun d8BenchmarksRunner(args: String): Promise<JsAny?> {
jsOne
}
}
-
-private fun isD8env(): JsBoolean =
- js("typeof isD8 !== 'undefined'")

View file

@ -0,0 +1,15 @@
diff --git a/Kotlin-compose/build/compose-benchmarks-benchmarks.uninstantiated.mjs b/Kotlin-compose/build/compose-benchmarks-benchmarks.uninstantiated.mjs
index f92d6ec..54cc4cf 100644
--- a/Kotlin-compose/build/compose-benchmarks-benchmarks.uninstantiated.mjs
+++ b/Kotlin-compose/build/compose-benchmarks-benchmarks.uninstantiated.mjs
@@ -111,8 +111,8 @@ export async function instantiate(imports={}, runInitializer=true) {
},
'kotlin.js.__convertKotlinClosureToJsClosure_(()->Unit)' : (f) => getCachedJsObject(f, () => wasmExports['__callFunction_(()->Unit)'](f, )),
'kotlin.js.jsThrow' : (e) => { throw e; },
- 'kotlin.io.printlnImpl' : (message) => console.log(message),
- 'kotlin.io.printError' : (error) => console.error(error),
+ 'kotlin.io.printlnImpl' : (message) => print(message),
+ 'kotlin.io.printError' : (error) => printErr(error),
'kotlin.js.jsArrayGet' : (array, index) => array[index],
'kotlin.js.jsArraySet' : (array, index, value) => { array[index] = value },
'kotlin.js.JsArray_$external_fun' : () => new Array(),

View file

@ -0,0 +1,13 @@
diff --git a/Kotlin-compose/build/compose-benchmarks-benchmarks.uninstantiated.mjs b/Kotlin-compose/build/compose-benchmarks-benchmarks.uninstantiated.mjs
index 3427ca2..882308d 100644
--- a/Kotlin-compose/build/compose-benchmarks-benchmarks.uninstantiated.mjs
+++ b/Kotlin-compose/build/compose-benchmarks-benchmarks.uninstantiated.mjs
@@ -383,7 +383,7 @@ export async function instantiate(imports={}, runInitializer=true) {
const wasmFilePath = './compose-benchmarks-benchmarks.wasm';
- const wasmTag = WebAssembly.JSTag ?? new WebAssembly.Tag({ parameters: ['externref'] });
+ const wasmTag = new WebAssembly.Tag({ parameters: ['externref'] });
const importObject = {
js_code,

View file

@ -0,0 +1,17 @@
diff --git a/Kotlin-compose/build/skiko.mjs b/Kotlin-compose/build/skiko.mjs
index ca8cabd..157890b 100644
--- a/Kotlin-compose/build/skiko.mjs
+++ b/Kotlin-compose/build/skiko.mjs
@@ -8325,6 +8325,7 @@ export const {
_releaseLocalCallbackScope
} = SkikoCallbacks;
+/*
export const loadedWasm = await loadSkikoWASM();
export const { GL } = loadedWasm;
@@ -9295,3 +9296,4 @@ export const {
skia_memSetDouble,
skia_memGetDouble,
} = loadedWasm.wasmExports;
+*/

View file

@ -0,0 +1,35 @@
diff --git a/benchmarks/multiplatform/build.gradle.kts b/benchmarks/multiplatform/build.gradle.kts
index 96ae9a60c7..25de0aead1 100644
--- a/benchmarks/multiplatform/build.gradle.kts
+++ b/benchmarks/multiplatform/build.gradle.kts
@@ -9,6 +9,7 @@ plugins {
allprojects {
repositories {
+ maven("https://packages.jetbrains.team/maven/p/kt/dev/org/jetbrains/kotlin/kotlin-gradle-plugin/")
google()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
diff --git a/benchmarks/multiplatform/gradle/libs.versions.toml b/benchmarks/multiplatform/gradle/libs.versions.toml
index 8713cf0de7..da803c319b 100644
--- a/benchmarks/multiplatform/gradle/libs.versions.toml
+++ b/benchmarks/multiplatform/gradle/libs.versions.toml
@@ -1,6 +1,6 @@
[versions]
compose-multiplatform = "1.8.2"
-kotlin = "2.2.0"
+kotlin = "2.2.20-Beta2"
kotlinx-coroutines = "1.8.0"
kotlinx-serialization = "1.8.0"
kotlinx-io = "0.7.0"
diff --git a/benchmarks/multiplatform/settings.gradle.kts b/benchmarks/multiplatform/settings.gradle.kts
index b1d1367534..84a894ed78 100644
--- a/benchmarks/multiplatform/settings.gradle.kts
+++ b/benchmarks/multiplatform/settings.gradle.kts
@@ -1,5 +1,6 @@
pluginManagement {
repositories {
+ maven("https://packages.jetbrains.team/maven/p/kt/dev/org/jetbrains/kotlin/kotlin-gradle-plugin/")
mavenLocal()
mavenCentral()
gradlePluginPortal()

View file

@ -1 +0,0 @@
print("Hello world!");

View file

@ -1,12 +0,0 @@
function firstWhere(list, fn) {
for (var x of list) {
if (fn(x)) {
return x;
}
}
return null;
}
nums = [1, 2, 3, 4, 5, 6, 7];
function isEven(x) { return (x & 1) == 0; }
firstEven = firstWhere(nums, isEven);
print('First even: ' + firstEven)

View file

@ -1,8 +0,0 @@
var n = 40000;
var i = 0;
var items = [];
while (i < n) {
items.push(i);
i = i + 1;
}

View file

@ -1,537 +0,0 @@
// Copyright 2006-2008 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// This is a JavaScript implementation of the Richards
// benchmark from:
//
// http://www.cl.cam.ac.uk/~mr10/Bench.html
//
// The benchmark was originally implemented in BCPL by
// Martin Richards.
/**
* The Richards benchmark simulates the task dispatcher of an
* operating system.
**/
function runRichards() {
var scheduler = new Scheduler();
scheduler.addIdleTask(ID_IDLE, 0, null, COUNT);
var queue = new Packet(null, ID_WORKER, KIND_WORK);
queue = new Packet(queue, ID_WORKER, KIND_WORK);
scheduler.addWorkerTask(ID_WORKER, 1000, queue);
queue = new Packet(null, ID_DEVICE_A, KIND_DEVICE);
queue = new Packet(queue, ID_DEVICE_A, KIND_DEVICE);
queue = new Packet(queue, ID_DEVICE_A, KIND_DEVICE);
scheduler.addHandlerTask(ID_HANDLER_A, 2000, queue);
queue = new Packet(null, ID_DEVICE_B, KIND_DEVICE);
queue = new Packet(queue, ID_DEVICE_B, KIND_DEVICE);
queue = new Packet(queue, ID_DEVICE_B, KIND_DEVICE);
scheduler.addHandlerTask(ID_HANDLER_B, 3000, queue);
scheduler.addDeviceTask(ID_DEVICE_A, 4000, null);
scheduler.addDeviceTask(ID_DEVICE_B, 5000, null);
scheduler.schedule();
if (scheduler.queueCount != EXPECTED_QUEUE_COUNT ||
scheduler.holdCount != EXPECTED_HOLD_COUNT) {
var msg =
"Error during execution: queueCount = " + scheduler.queueCount +
", holdCount = " + scheduler.holdCount + ".";
throw new Error(msg);
}
}
var COUNT = 1000;
/**
* These two constants specify how many times a packet is queued and
* how many times a task is put on hold in a correct run of richards.
* They don't have any meaning a such but are characteristic of a
* correct run so if the actual queue or hold count is different from
* the expected there must be a bug in the implementation.
**/
var EXPECTED_QUEUE_COUNT = 2322;
var EXPECTED_HOLD_COUNT = 928;
/**
* A scheduler can be used to schedule a set of tasks based on their relative
* priorities. Scheduling is done by maintaining a list of task control blocks
* which holds tasks and the data queue they are processing.
* @constructor
*/
function Scheduler() {
this.queueCount = 0;
this.holdCount = 0;
this.blocks = new Array(NUMBER_OF_IDS);
this.list = null;
this.currentTcb = null;
this.currentId = null;
}
var ID_IDLE = 0;
var ID_WORKER = 1;
var ID_HANDLER_A = 2;
var ID_HANDLER_B = 3;
var ID_DEVICE_A = 4;
var ID_DEVICE_B = 5;
var NUMBER_OF_IDS = 6;
var KIND_DEVICE = 0;
var KIND_WORK = 1;
/**
* Add an idle task to this scheduler.
* @param {int} id the identity of the task
* @param {int} priority the task's priority
* @param {Packet} queue the queue of work to be processed by the task
* @param {int} count the number of times to schedule the task
*/
Scheduler.prototype.addIdleTask = function (id, priority, queue, count) {
this.addRunningTask(id, priority, queue, new IdleTask(this, 1, count));
};
/**
* Add a work task to this scheduler.
* @param {int} id the identity of the task
* @param {int} priority the task's priority
* @param {Packet} queue the queue of work to be processed by the task
*/
Scheduler.prototype.addWorkerTask = function (id, priority, queue) {
this.addTask(id, priority, queue, new WorkerTask(this, ID_HANDLER_A, 0));
};
/**
* Add a handler task to this scheduler.
* @param {int} id the identity of the task
* @param {int} priority the task's priority
* @param {Packet} queue the queue of work to be processed by the task
*/
Scheduler.prototype.addHandlerTask = function (id, priority, queue) {
this.addTask(id, priority, queue, new HandlerTask(this));
};
/**
* Add a handler task to this scheduler.
* @param {int} id the identity of the task
* @param {int} priority the task's priority
* @param {Packet} queue the queue of work to be processed by the task
*/
Scheduler.prototype.addDeviceTask = function (id, priority, queue) {
this.addTask(id, priority, queue, new DeviceTask(this))
};
/**
* Add the specified task and mark it as running.
* @param {int} id the identity of the task
* @param {int} priority the task's priority
* @param {Packet} queue the queue of work to be processed by the task
* @param {Task} task the task to add
*/
Scheduler.prototype.addRunningTask = function (id, priority, queue, task) {
this.addTask(id, priority, queue, task);
this.currentTcb.setRunning();
};
/**
* Add the specified task to this scheduler.
* @param {int} id the identity of the task
* @param {int} priority the task's priority
* @param {Packet} queue the queue of work to be processed by the task
* @param {Task} task the task to add
*/
Scheduler.prototype.addTask = function (id, priority, queue, task) {
this.currentTcb = new TaskControlBlock(this.list, id, priority, queue, task);
this.list = this.currentTcb;
this.blocks[id] = this.currentTcb;
};
/**
* Execute the tasks managed by this scheduler.
*/
Scheduler.prototype.schedule = function () {
this.currentTcb = this.list;
while (this.currentTcb != null) {
if (this.currentTcb.isHeldOrSuspended()) {
this.currentTcb = this.currentTcb.link;
} else {
this.currentId = this.currentTcb.id;
this.currentTcb = this.currentTcb.run();
}
}
};
/**
* Release a task that is currently blocked and return the next block to run.
* @param {int} id the id of the task to suspend
*/
Scheduler.prototype.release = function (id) {
var tcb = this.blocks[id];
if (tcb == null) return tcb;
tcb.markAsNotHeld();
if (tcb.priority > this.currentTcb.priority) {
return tcb;
} else {
return this.currentTcb;
}
};
/**
* Block the currently executing task and return the next task control block
* to run. The blocked task will not be made runnable until it is explicitly
* released, even if new work is added to it.
*/
Scheduler.prototype.holdCurrent = function () {
this.holdCount++;
this.currentTcb.markAsHeld();
return this.currentTcb.link;
};
/**
* Suspend the currently executing task and return the next task control block
* to run. If new work is added to the suspended task it will be made runnable.
*/
Scheduler.prototype.suspendCurrent = function () {
this.currentTcb.markAsSuspended();
return this.currentTcb;
};
/**
* Add the specified packet to the end of the worklist used by the task
* associated with the packet and make the task runnable if it is currently
* suspended.
* @param {Packet} packet the packet to add
*/
Scheduler.prototype.queue = function (packet) {
var t = this.blocks[packet.id];
if (t == null) return t;
this.queueCount++;
packet.link = null;
packet.id = this.currentId;
return t.checkPriorityAdd(this.currentTcb, packet);
};
/**
* A task control block manages a task and the queue of work packages associated
* with it.
* @param {TaskControlBlock} link the preceding block in the linked block list
* @param {int} id the id of this block
* @param {int} priority the priority of this block
* @param {Packet} queue the queue of packages to be processed by the task
* @param {Task} task the task
* @constructor
*/
function TaskControlBlock(link, id, priority, queue, task) {
this.link = link;
this.id = id;
this.priority = priority;
this.queue = queue;
this.task = task;
if (queue == null) {
this.state = STATE_SUSPENDED;
} else {
this.state = STATE_SUSPENDED_RUNNABLE;
}
}
/**
* The task is running and is currently scheduled.
*/
var STATE_RUNNING = 0;
/**
* The task has packets left to process.
*/
var STATE_RUNNABLE = 1;
/**
* The task is not currently running. The task is not blocked as such and may
* be started by the scheduler.
*/
var STATE_SUSPENDED = 2;
/**
* The task is blocked and cannot be run until it is explicitly released.
*/
var STATE_HELD = 4;
var STATE_SUSPENDED_RUNNABLE = STATE_SUSPENDED | STATE_RUNNABLE;
var STATE_NOT_HELD = ~STATE_HELD;
TaskControlBlock.prototype.setRunning = function () {
this.state = STATE_RUNNING;
};
TaskControlBlock.prototype.markAsNotHeld = function () {
this.state = this.state & STATE_NOT_HELD;
};
TaskControlBlock.prototype.markAsHeld = function () {
this.state = this.state | STATE_HELD;
};
TaskControlBlock.prototype.isHeldOrSuspended = function () {
return (this.state & STATE_HELD) != 0 || (this.state == STATE_SUSPENDED);
};
TaskControlBlock.prototype.markAsSuspended = function () {
this.state = this.state | STATE_SUSPENDED;
};
TaskControlBlock.prototype.markAsRunnable = function () {
this.state = this.state | STATE_RUNNABLE;
};
/**
* Runs this task, if it is ready to be run, and returns the next task to run.
*/
TaskControlBlock.prototype.run = function () {
var packet;
if (this.state == STATE_SUSPENDED_RUNNABLE) {
packet = this.queue;
this.queue = packet.link;
if (this.queue == null) {
this.state = STATE_RUNNING;
} else {
this.state = STATE_RUNNABLE;
}
} else {
packet = null;
}
return this.task.run(packet);
};
/**
* Adds a packet to the worklist of this block's task, marks this as runnable if
* necessary, and returns the next runnable object to run (the one
* with the highest priority).
*/
TaskControlBlock.prototype.checkPriorityAdd = function (task, packet) {
if (this.queue == null) {
this.queue = packet;
this.markAsRunnable();
if (this.priority > task.priority) return this;
} else {
this.queue = packet.addTo(this.queue);
}
return task;
};
TaskControlBlock.prototype.toString = function () {
return "tcb { " + this.task + "@" + this.state + " }";
};
/**
* An idle task doesn't do any work itself but cycles control between the two
* device tasks.
* @param {Scheduler} scheduler the scheduler that manages this task
* @param {int} v1 a seed value that controls how the device tasks are scheduled
* @param {int} count the number of times this task should be scheduled
* @constructor
*/
function IdleTask(scheduler, v1, count) {
this.scheduler = scheduler;
this.v1 = v1;
this.count = count;
}
IdleTask.prototype.run = function (packet) {
this.count--;
if (this.count == 0) return this.scheduler.holdCurrent();
if ((this.v1 & 1) == 0) {
this.v1 = this.v1 >> 1;
return this.scheduler.release(ID_DEVICE_A);
} else {
this.v1 = (this.v1 >> 1) ^ 0xD008;
return this.scheduler.release(ID_DEVICE_B);
}
};
IdleTask.prototype.toString = function () {
return "IdleTask"
};
/**
* A task that suspends itself after each time it has been run to simulate
* waiting for data from an external device.
* @param {Scheduler} scheduler the scheduler that manages this task
* @constructor
*/
function DeviceTask(scheduler) {
this.scheduler = scheduler;
this.v1 = null;
}
DeviceTask.prototype.run = function (packet) {
if (packet == null) {
if (this.v1 == null) return this.scheduler.suspendCurrent();
var v = this.v1;
this.v1 = null;
return this.scheduler.queue(v);
} else {
this.v1 = packet;
return this.scheduler.holdCurrent();
}
};
DeviceTask.prototype.toString = function () {
return "DeviceTask";
};
/**
* A task that manipulates work packets.
* @param {Scheduler} scheduler the scheduler that manages this task
* @param {int} v1 a seed used to specify how work packets are manipulated
* @param {int} v2 another seed used to specify how work packets are manipulated
* @constructor
*/
function WorkerTask(scheduler, v1, v2) {
this.scheduler = scheduler;
this.v1 = v1;
this.v2 = v2;
}
WorkerTask.prototype.run = function (packet) {
if (packet == null) {
return this.scheduler.suspendCurrent();
} else {
if (this.v1 == ID_HANDLER_A) {
this.v1 = ID_HANDLER_B;
} else {
this.v1 = ID_HANDLER_A;
}
packet.id = this.v1;
packet.a1 = 0;
for (var i = 0; i < DATA_SIZE; i++) {
this.v2++;
if (this.v2 > 26) this.v2 = 1;
packet.a2[i] = this.v2;
}
return this.scheduler.queue(packet);
}
};
WorkerTask.prototype.toString = function () {
return "WorkerTask";
};
/**
* A task that manipulates work packets and then suspends itself.
* @param {Scheduler} scheduler the scheduler that manages this task
* @constructor
*/
function HandlerTask(scheduler) {
this.scheduler = scheduler;
this.v1 = null;
this.v2 = null;
}
HandlerTask.prototype.run = function (packet) {
if (packet != null) {
if (packet.kind == KIND_WORK) {
this.v1 = packet.addTo(this.v1);
} else {
this.v2 = packet.addTo(this.v2);
}
}
if (this.v1 != null) {
var count = this.v1.a1;
var v;
if (count < DATA_SIZE) {
if (this.v2 != null) {
v = this.v2;
this.v2 = this.v2.link;
v.a1 = this.v1.a2[count];
this.v1.a1 = count + 1;
return this.scheduler.queue(v);
}
} else {
v = this.v1;
this.v1 = this.v1.link;
return this.scheduler.queue(v);
}
}
return this.scheduler.suspendCurrent();
};
HandlerTask.prototype.toString = function () {
return "HandlerTask";
};
/* --- *
* P a c k e t
* --- */
var DATA_SIZE = 4;
/**
* A simple package of data that is manipulated by the tasks. The exact layout
* of the payload data carried by a packet is not importaint, and neither is the
* nature of the work performed on packets by the tasks.
*
* Besides carrying data, packets form linked lists and are hence used both as
* data and worklists.
* @param {Packet} link the tail of the linked list of packets
* @param {int} id an ID for this packet
* @param {int} kind the type of this packet
* @constructor
*/
function Packet(link, id, kind) {
this.link = link;
this.id = id;
this.kind = kind;
this.a1 = 0;
this.a2 = new Array(DATA_SIZE);
}
/**
* Add this packet to the end of a worklist, and return the worklist.
* @param {Packet} queue the worklist to add this packet to
*/
Packet.prototype.addTo = function (queue) {
this.link = null;
if (queue == null) return this;
var peek, next = queue;
while ((peek = next.link) != null)
next = peek;
next.link = this;
return queue;
};
Packet.prototype.toString = function () {
return "Packet";
};
runRichards();

View file

@ -1,8 +0,0 @@
var n = 40000;
var i = 0;
var items = [];
while (i < n) {
items.push("digit" + i);
i = i + 1;
}

View file

@ -46,9 +46,7 @@ class Benchmark {
const playerExpectations = getPlayerExpectations(iterations)
if (this._players.length != playerExpectations.length)
throw "Expect " + playerExpectations.length + ", but actually have " + this._players.length;
if (isInBrowser) {
for (let playerIdx = 0; playerIdx < playerExpectations.length; playerIdx++)
playerExpectations[playerIdx].validate(this._players[playerIdx]);
}
for (let playerIdx = 0; playerIdx < playerExpectations.length; playerIdx++)
playerExpectations[playerIdx].validate(this._players[playerIdx]);
}
}

View file

@ -24,16 +24,8 @@
*/
"use strict";
let currentTime;
if (this.performance && performance.now)
currentTime = function() { return performance.now() };
else if (this.preciseTime)
currentTime = function() { return preciseTime() * 1000; };
else
currentTime = function() { return +new Date(); };
class Benchmark {
constructor(verbose = 0)
constructor({verbose = 0})
{
this._verbose = verbose;
}
@ -43,9 +35,9 @@ class Benchmark {
this.setup();
for (let iteration = 0; iteration < numIterations; ++iteration) {
let before = currentTime();
let before = performance.now();
this.runOnce();
let after = currentTime();
let after = performance.now();
results.push(after - before);
}

View file

@ -57,12 +57,12 @@ function ShiftRows(s, Nb) { // shift row r of state S left by r bytes [§5.1.
function MixColumns(s, Nb) { // combine bytes of each col of state S [§5.1.3]
for (var c=0; c<4; c++) {
var a = new Array(4); // 'a' is a copy of the current column from 's'
var b = new Array(4); // 'b' is a•{02} in GF(2^8)
var b = new Array(4); // 'b' is a dot {02} in GF(2^8)
for (var i=0; i<4; i++) {
a[i] = s[i][c];
b[i] = s[i][c]&0x80 ? s[i][c]<<1 ^ 0x011b : s[i][c]<<1;
}
// a[n] ^ b[n] is a•{03} in GF(2^8)
// a[n] ^ b[n] is a dot {03} in GF(2^8)
s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // 2*a0 + 3*a1 + a2 + a3
s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3
s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3

View file

@ -3,7 +3,7 @@
// Copyright (c) 2019 Paul Miller (https://paulmillr.com)
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the “Software”), to deal
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
@ -12,7 +12,7 @@
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

View file

@ -3,7 +3,7 @@
// Copyright (c) 2019 Paul Miller (https://paulmillr.com)
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the “Software”), to deal
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
@ -12,7 +12,7 @@
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

View file

@ -3,7 +3,7 @@
// Copyright (c) 2019 Paul Miller (https://paulmillr.com)
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the “Software”), to deal
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
@ -12,7 +12,7 @@
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

View file

@ -32,7 +32,7 @@ function benchmarkImpl(configuration) {
var simulator = new Simulator(numAircraft);
var detector = new CollisionDetector();
var lastTime = currentTime();
var lastTime = performance.now();
var results = [];
for (var i = 0; i < numFrames; ++i) {
var time = i / 10;
@ -40,7 +40,7 @@ function benchmarkImpl(configuration) {
var collisions = detector.handleNewFrame(simulator.simulate(time));
var before = lastTime;
var after = currentTime();
var after = performance.now();
lastTime = after;
var result = {
time: after - before,

View file

@ -73,10 +73,3 @@ function averageAbovePercentile(numbers, percentile) {
return result;
}
var currentTime;
if (this.performance && performance.now)
currentTime = function() { return performance.now() };
else if (preciseTime)
currentTime = function() { return preciseTime() * 1000; };
else
currentTime = function() { return 0 + new Date(); };

87
cli.js
View file

@ -23,35 +23,44 @@
* THE POSSIBILITY OF SUCH DAMAGE.
*/
const isInBrowser = false;
console = {
log: globalThis?.console?.log ?? print,
error: globalThis?.console?.error ?? print,
load("./shell-config.js")
const cliFlags = { __proto__: null };
const cliArgs = [];
if (globalThis.arguments?.length) {
for (const argument of globalThis.arguments)
if (argument.startsWith("--")) {
const parts = argument.split("=");
cliFlags[parts[0].toLowerCase()] = parts.slice(1).join("=");
} else
cliArgs.push(argument);
}
const isD8 = typeof Realm !== "undefined";
if (isD8)
globalThis.readFile = read;
const isSpiderMonkey = typeof newGlobal !== "undefined";
if (isSpiderMonkey) {
globalThis.readFile = readRelativeToScript;
globalThis.arguments = scriptArgs;
function getIntFlag(flags, flag) {
if (!(flag in flags))
return undefined;
const rawValue = flags[flag];
const value = parseInt(rawValue);
if (value <= 0)
throw new Error(`Expected positive value for ${flag}, but got ${rawValue}`);
return value;
}
if (typeof arguments !== "undefined" && arguments.length > 0)
testList = arguments.slice();
if (typeof testList === "undefined")
testList = undefined;
if (typeof testIterationCount === "undefined")
testIterationCount = undefined;
if ("--iteration-count" in cliFlags)
globalThis.testIterationCount = getIntFlag(cliFlags, "--iteration-count");
if ("--worst-case-count" in cliFlags)
globalThis.testWorstCaseCount = getIntFlag(cliFlags, "--worst-case-count");
if ("--dump-json-results" in cliFlags)
globalThis.dumpJSONResults = true;
if (typeof runMode !== "undefined" && runMode == "RAMification")
RAMification = true;
else
RAMification = false;
globalThis.RAMification = true;
if ("--ramification" in cliFlags)
globalThis.RAMification = true;
if ("--no-prefetch" in cliFlags)
globalThis.prefetchResources = false;
if (cliArgs.length)
globalThis.testList = cliArgs;
load("./JetStreamDriver.js");
async function runJetStream() {
try {
@ -63,4 +72,34 @@ async function runJetStream() {
throw e;
}
}
runJetStream();
load("./JetStreamDriver.js");
if ("--help" in cliFlags) {
console.log("JetStream Driver Help");
console.log("");
console.log("Options:");
console.log(" --iteration-count: Set the default iteration count.");
console.log(" --worst-case-count: Set the default worst-case count");
console.log(" --dump-json-results: Print summary json to the console.");
console.log(" --dump-test-list: Print test list instead of running.");
console.log(" --ramification: Enable ramification support. See RAMification.py for more details.");
console.log(" --no-prefetch: Do not prefetch resources. Will add network overhead to measurements!");
console.log("");
console.log("Available tags:");
const tagNames = Array.from(benchmarksByTag.keys()).sort();
for (const tagName of tagNames)
console.log(" ", tagName);
console.log("");
console.log("Available tests:");
const benchmarkNames = BENCHMARKS.map(b => b.name).sort();
for (const benchmark of benchmarkNames)
console.log(" ", benchmark);
} else if ("--dump-test-list" in cliFlags) {
JetStream.dumpTestList();
} else {
runJetStream();
}

View file

@ -26,7 +26,7 @@
let indirectEval = eval;
class Benchmark {
async init() {
this.inspectorText = `let _____top_level_____ = ${Math.random()}; ${await getString(inspectorPayloadBlob)}`;
this.inspectorText = `let _____top_level_____ = ${Math.random()}; ${await JetStream.getString(JetStream.preload.inspectorPayloadBlob)}`;
this.index = 0;
}

View file

@ -26,7 +26,7 @@
let indirectEval = eval;
class Benchmark {
async init() {
this.inspectorText = `let _____top_level_____ = ${Math.random()}; ${await getString(inspectorPayloadBlob)}`;
this.inspectorText = `let _____top_level_____ = ${Math.random()}; ${await JetStream.getString(JetStream.preload.inspectorPayloadBlob)}`;
this.index = 0;
}

View file

@ -56,9 +56,13 @@ class File {
set data(dataView) { this._data = dataView; }
swapByteOrder() {
let hash = 0x1a2b3c4d;
for (let i = 0; i < Math.floor(this.data.byteLength / 8) * 8; i += 8) {
this.data.setFloat64(i, this.data.getFloat64(i, isLittleEndian), !isLittleEndian);
const data = this.data.getFloat64(i, isLittleEndian);
this.data.setFloat64(i, data, !isLittleEndian);
hash ^= data | 0;
}
return hash;
}
}
@ -171,11 +175,16 @@ async function setupDirectory() {
}
class Benchmark {
EXPECTED_FILE_COUNT = 666;
totalFileCount = 0;
lastFileHash = undefined;
async runIteration() {
const fs = await setupDirectory();
for await (let { entry: file } of fs.forEachFileRecursively()) {
file.swapByteOrder();
this.lastFileHash = file.swapByteOrder();
}
for await (let { name, entry: dir } of fs.forEachDirectoryRecursively()) {
@ -187,5 +196,17 @@ class Benchmark {
}
}
}
for await (let _ of fs.forEachFileRecursively()) {
this.totalFileCount++;
}
}
validate(iterations) {
const expectedFileCount = this.EXPECTED_FILE_COUNT * iterations;
if (this.totalFileCount != expectedFileCount)
throw new Error(`Invalid total file count ${this.totalFileCount}, expected ${expectedFileCount}.`);
if (this.lastFileHash === undefined)
throw new Error(`Invalid file hash: ${this.lastFileHash}`);
}
}

View file

@ -34,15 +34,19 @@ function computeIsLittleEndian() {
}
const isLittleEndian = computeIsLittleEndian();
let globalCounter = 0;
function randomFileContents(bytes = ((Math.random() * 128) >>> 0) + 2056) {
let result = new ArrayBuffer(bytes);
function randomFileContents() {
const numBytes = (globalCounter % 128) + 2056;
globalCounter++;
let result = new ArrayBuffer(numBytes);
let view = new Uint8Array(result);
for (let i = 0; i < bytes; ++i)
view[i] = (Math.random() * 255) >>> 0;
for (let i = 0; i < numBytes; ++i)
view[i] = (i + globalCounter) % 255;
return new DataView(result);
}
class File {
constructor(dataView, permissions) {
this._data = dataView;
@ -53,9 +57,13 @@ class File {
set data(dataView) { this._data = dataView; }
swapByteOrder() {
let hash = 0x1a2b3c4d;
for (let i = 0; i < Math.floor(this.data.byteLength / 8) * 8; i += 8) {
this.data.setFloat64(i, this.data.getFloat64(i, isLittleEndian), !isLittleEndian);
const data = this.data.getFloat64(i, isLittleEndian);
this.data.setFloat64(i, data, !isLittleEndian);
hash ^= data | 0;
}
return hash;
}
}
@ -141,19 +149,22 @@ class Directory {
function setupDirectory() {
const fs = new Directory;
let dirs = [fs];
let counter = 0;
for (let dir of dirs) {
for (let i = 0; i < 8; ++i) {
if (dirs.length < 250 && Math.random() >= 0.3) {
for (let i = 0; i < 10; ++i) {
if (dirs.length < 400 && (counter % 3) <= 1) {
dirs.push(dir.addDirectory(`dir-${i}`));
}
counter++;
}
}
for (let dir of dirs) {
for (let i = 0; i < 5; ++i) {
if (Math.random() >= 0.6) {
if ((counter % 3) === 0) {
dir.addFile(`file-${i}`, new File(randomFileContents()));
}
counter++;
}
}
@ -161,11 +172,16 @@ function setupDirectory() {
}
class Benchmark {
EXPECTED_FILE_COUNT = 666;
totalFileCount = 0;
lastFileHash = undefined;
runIteration() {
const fs = setupDirectory();
for (let { entry: file } of fs.forEachFileRecursively()) {
file.swapByteOrder();
this.lastFileHash = file.swapByteOrder();
}
for (let { name, entry: dir } of fs.forEachDirectoryRecursively()) {
@ -178,5 +194,17 @@ class Benchmark {
}
}
}
for (let _ of fs.forEachFileRecursively()) {
this.totalFileCount++;
}
}
validate(iterations) {
const expectedFileCount = this.EXPECTED_FILE_COUNT * iterations;
if (this.totalFileCount != expectedFileCount)
throw new Error(`Invalid total file count ${this.totalFileCount}, expected ${expectedFileCount}.`);
if (this.lastFileHash === undefined)
throw new Error(`Invalid file hash: ${this.lastFileHash}`);
}
}

View file

@ -32,13 +32,13 @@
<link rel="stylesheet" href="JetStream.css">
</head>
<body>
<body class="overflow-scroll">
<h1 class="logo">
<div id="jetstreams">
<a href="index.html" class="logo-image">JetStream 3</a>
</div>
</h1>
<main>
<main class="overflow-visible">
<article>
<h2>In-Depth Analysis</h2>
@ -776,6 +776,20 @@
Source code: <a href="proxy/vue-benchmark.js">vue-benchmark.js</a>
</dd>
<dt id="dotnet-interp-wasm">dotnet-interp-wasm</dt>
<dd>
Tests <a href="https://github.com/dotnet/runtime">.NET on WebAssembly</a>. This benchmark tests operations
on .NET implementation of String, JSON serialization, specifics of .NET exceptions and computation
of a 3D scene using Mono Interpreter. Source code: <a href="wasm/dotnet">.NET</a>.
</dd>
<dt id="dotnet-aot-wasm">dotnet-aot-wasm</dt>
<dd>
Tests <a href="https://github.com/dotnet/runtime">.NET on WebAssembly</a>. This benchmark tests operations
on .NET implementation of String, JSON serialization, specifics of .NET exceptions and computation
of a 3D scene using Mono AOT. Source code: <a href="wasm/dotnet">.NET</a>.
</dd>
</dl>
<p><a href="index.html" class="button">&larr; Return to Tests</a></p>

517
package-lock.json generated
View file

@ -17,11 +17,12 @@
"command-line-usage": "^6.1.3",
"es-main": "^1.3.0",
"eslint": "^8.38.0",
"glob": "^11.0.3",
"http-server": "^14.1.1",
"jsvu": "^2.5.1",
"local-web-server": "^5.4.0",
"prettier": "^2.8.3",
"selenium-webdriver": "^4.8.0"
"selenium-webdriver": "^4.35.0"
},
"engines": {
"node": ">=22.0.0",
@ -607,6 +608,101 @@
"deprecated": "Use @eslint/object-schema instead",
"dev": true
},
"node_modules/@isaacs/balanced-match": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@isaacs/brace-expansion": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@isaacs/balanced-match": "^4.0.1"
},
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz",
"integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true,
"license": "MIT"
},
"node_modules/@isaacs/cliui/node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
@ -1845,6 +1941,13 @@
"integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==",
"dev": true
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true,
"license": "MIT"
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -2473,6 +2576,36 @@
}
}
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/foreground-child/node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@ -2509,7 +2642,8 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
"dev": true,
"license": "ISC"
},
"node_modules/function-bind": {
"version": "1.1.2",
@ -2582,21 +2716,24 @@
}
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
"integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
"dev": true,
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
"foreground-child": "^3.3.1",
"jackspeak": "^4.1.1",
"minimatch": "^10.0.3",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^2.0.0"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": "*"
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@ -2614,6 +2751,32 @@
"node": ">=10.13.0"
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
"integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
"dev": true,
"license": "ISC",
"dependencies": {
"@isaacs/brace-expansion": "^5.0.0"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob/node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/global-dirs": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
@ -3041,6 +3204,7 @@
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"dev": true,
"license": "ISC",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
@ -3290,6 +3454,22 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
"node_modules/jackspeak": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
"integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -3418,6 +3598,28 @@
"node": ">=18.0.0"
}
},
"node_modules/jsvu/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true,
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
@ -4544,6 +4746,13 @@
"node": ">=8"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"dev": true,
"license": "BlueOak-1.0.0"
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@ -4604,6 +4813,43 @@
"node": ">=8"
}
},
"node_modules/path-scurry": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz",
"integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==",
"dev": true,
"license": "ISC",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/path-scurry/node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/path-to-regexp": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
@ -5028,6 +5274,28 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rimraf/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true,
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/run-async": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
@ -5108,9 +5376,9 @@
"dev": true
},
"node_modules/selenium-webdriver": {
"version": "4.27.0",
"resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.27.0.tgz",
"integrity": "sha512-LkTJrNz5socxpPnWPODQ2bQ65eYx9JK+DQMYNihpTjMCqHwgWGYQnQTCAAche2W3ZP87alA+1zYPvgS8tHNzMQ==",
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.35.0.tgz",
"integrity": "sha512-Baaeiuyu7BIIsSYf0SI7Mi55gsNmdI00KM0Hcofw1RnAY+0QEVpdh5yAxueDxgTZS8vcbGZFU0NJ6Qc1riIrLg==",
"dev": true,
"funding": [
{
@ -5122,14 +5390,15 @@
"url": "https://opencollective.com/selenium"
}
],
"license": "Apache-2.0",
"dependencies": {
"@bazel/runfiles": "^6.3.1",
"jszip": "^3.10.1",
"tmp": "^0.2.3",
"ws": "^8.18.0"
"ws": "^8.18.2"
},
"engines": {
"node": ">= 14.21.0"
"node": ">= 20.0.0"
}
},
"node_modules/semver": {
@ -5412,6 +5681,32 @@
"node": ">=4"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/string-width/node_modules/ansi-regex": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
@ -5445,6 +5740,20 @@
"node": ">=8"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-eof": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
@ -6098,6 +6407,171 @@
"node": ">=8"
}
},
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/wrap-ansi-cjs/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT"
},
"node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi/node_modules/ansi-regex": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz",
"integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi/node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true,
"license": "MIT"
},
"node_modules/wrap-ansi/node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/wrap-ansi/node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@ -6116,10 +6590,11 @@
}
},
"node_modules/ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},

View file

@ -16,18 +16,21 @@
"license": "SEE LICENSE IN LICENSE",
"scripts": {
"server": "node tests/server.mjs",
"compress": "node utils/compress.mjs",
"lint:check": "eslint **/*.{js,mjs,jsx,ts,tsx}",
"pretty:check": "prettier --check ./",
"format:check": "npm run pretty:check && npm run lint:check",
"test:chrome": "node tests/run.mjs --browser chrome",
"test:firefox": "node tests/run.mjs --browser firefox",
"test:safari": "node tests/run.mjs --browser safari",
"test:edge": "node tests/run.mjs --browser edge",
"test:chrome": "node tests/run-browser.mjs --browser chrome",
"test:firefox": "node tests/run-browser.mjs --browser firefox",
"test:safari": "node tests/run-browser.mjs --browser safari",
"test:edge": "node tests/run-browser.mjs --browser edge",
"test:shell": "npm run test:v8 && npm run test:jsc && npm run test:spidermonkey",
"test:v8": "node tests/run-shell.mjs --shell v8",
"test:jsc": "node tests/run-shell.mjs --shell jsc",
"test:spidermonkey": "node tests/run-shell.mjs --shell spidermonkey"
},
"devDependencies": {
"@actions/core": "^1.11.1",
"@babel/core": "^7.21.3",
"@babel/eslint-parser": "^7.21.3",
"@babel/plugin-proposal-decorators": "^7.21.0",
@ -35,11 +38,11 @@
"command-line-usage": "^6.1.3",
"es-main": "^1.3.0",
"eslint": "^8.38.0",
"glob": "^11.0.3",
"http-server": "^14.1.1",
"jsvu": "^2.5.1",
"local-web-server": "^5.4.0",
"prettier": "^2.8.3",
"selenium-webdriver": "^4.8.0",
"@actions/core": "^1.11.1"
"selenium-webdriver": "^4.35.0"
}
}

3
polyfills/README Normal file
View file

@ -0,0 +1,3 @@
Polyfills shared across different workloads, for browser APIs that are not available in JavaScript engine shells, e.g., `TextDecoder`.
To make performance between browser and shell measurements not too dissimilar, these shouldn't account for a major part of the runtime (e.g., ideally only be used during initialization, outside of the main measurement loop).
Also, please also add a link to the original sources and licensing information in each subdirectory.

View file

@ -0,0 +1,286 @@
/*
* Copyright 2017 Sam Thorogood. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
/**
* @fileoverview Polyfill for TextEncoder and TextDecoder.
*
* You probably want `text.min.js`, and not this file directly.
*/
//JSC
const global = (0,eval)("this");
(function(scope) {
'use strict';
// fail early
if (scope['TextEncoder'] && scope['TextDecoder']) {
return false;
}
// used for FastTextDecoder
const validUtfLabels = ['utf-8', 'utf8', 'unicode-1-1-utf-8'];
/**
* @constructor
*/
function FastTextEncoder() {
// This does not accept an encoding, and always uses UTF-8:
// https://www.w3.org/TR/encoding/#dom-textencoder
}
Object.defineProperty(FastTextEncoder.prototype, 'encoding', {value: 'utf-8'});
/**
* @param {string} string
* @param {{stream: boolean}=} options
* @return {!Uint8Array}
*/
FastTextEncoder.prototype['encode'] = function(string, options={stream: false}) {
if (options.stream) {
throw new Error(`Failed to encode: the 'stream' option is unsupported.`);
}
let pos = 0;
const len = string.length;
let at = 0; // output position
let tlen = Math.max(32, len + (len >>> 1) + 7); // 1.5x size
let target = new Uint8Array((tlen >>> 3) << 3); // ... but at 8 byte offset
while (pos < len) {
let value = string.charCodeAt(pos++);
if (value >= 0xd800 && value <= 0xdbff) {
// high surrogate
if (pos < len) {
const extra = string.charCodeAt(pos);
if ((extra & 0xfc00) === 0xdc00) {
++pos;
value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000;
}
}
if (value >= 0xd800 && value <= 0xdbff) {
continue; // drop lone surrogate
}
}
// expand the buffer if we couldn't write 4 bytes
if (at + 4 > target.length) {
tlen += 8; // minimum extra
tlen *= (1.0 + (pos / string.length) * 2); // take 2x the remaining
tlen = (tlen >>> 3) << 3; // 8 byte offset
const update = new Uint8Array(tlen);
update.set(target);
target = update;
}
if ((value & 0xffffff80) === 0) { // 1-byte
target[at++] = value; // ASCII
continue;
} else if ((value & 0xfffff800) === 0) { // 2-byte
target[at++] = ((value >>> 6) & 0x1f) | 0xc0;
} else if ((value & 0xffff0000) === 0) { // 3-byte
target[at++] = ((value >>> 12) & 0x0f) | 0xe0;
target[at++] = ((value >>> 6) & 0x3f) | 0x80;
} else if ((value & 0xffe00000) === 0) { // 4-byte
target[at++] = ((value >>> 18) & 0x07) | 0xf0;
target[at++] = ((value >>> 12) & 0x3f) | 0x80;
target[at++] = ((value >>> 6) & 0x3f) | 0x80;
} else {
continue; // out of range
}
target[at++] = (value & 0x3f) | 0x80;
}
// Use subarray if slice isn't supported (IE11). This will use more memory
// because the original array still exists.
return target.slice ? target.slice(0, at) : target.subarray(0, at);
}
/**
* @constructor
* @param {string=} utfLabel
* @param {{fatal: boolean}=} options
*/
function FastTextDecoder(utfLabel='utf-8', options={fatal: false}) {
if (validUtfLabels.indexOf(utfLabel.toLowerCase()) === -1) {
throw new RangeError(
`Failed to construct 'TextDecoder': The encoding label provided ('${utfLabel}') is invalid.`);
}
// if (options.fatal) {
// throw new Error(`Failed to construct 'TextDecoder': the 'fatal' option is unsupported.`);
// }
}
Object.defineProperty(FastTextDecoder.prototype, 'encoding', {value: 'utf-8'});
Object.defineProperty(FastTextDecoder.prototype, 'fatal', {value: false});
Object.defineProperty(FastTextDecoder.prototype, 'ignoreBOM', {value: false});
/**
* @param {!Uint8Array} bytes
* @return {string}
*/
function decodeBuffer(bytes) {
return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString('utf-8');
}
/**
* @param {!Uint8Array} bytes
* @return {string}
*/
function decodeSyncXHR(bytes) {
const b = new Blob([bytes], {type: 'text/plain;charset=UTF-8'});
const u = URL.createObjectURL(b);
// This hack will fail in non-Edgium Edge because sync XHRs are disabled (and
// possibly in other places), so ensure there's a fallback call.
try {
const x = new XMLHttpRequest();
x.open('GET', u, false);
x.send();
return x.responseText;
} catch (e) {
return decodeFallback(bytes);
} finally {
URL.revokeObjectURL(u);
}
}
/**
* @param {!Uint8Array} bytes
* @return {string}
*/
function decodeFallback(bytes) {
let inputIndex = 0;
// Create a working buffer for UTF-16 code points, but don't generate one
// which is too large for small input sizes. UTF-8 to UCS-16 conversion is
// going to be at most 1:1, if all code points are ASCII. The other extreme
// is 4-byte UTF-8, which results in two UCS-16 points, but this is still 50%
// fewer entries in the output.
const pendingSize = Math.min(256 * 256, bytes.length + 1);
const pending = new Uint16Array(pendingSize);
const chunks = [];
let pendingIndex = 0;
for (;;) {
const more = inputIndex < bytes.length;
// If there's no more data or there'd be no room for two UTF-16 values,
// create a chunk. This isn't done at the end by simply slicing the data
// into equal sized chunks as we might hit a surrogate pair.
if (!more || (pendingIndex >= pendingSize - 1)) {
// nb. .apply and friends are *really slow*. Low-hanging fruit is to
// expand this to literally pass pending[0], pending[1], ... etc, but
// the output code expands pretty fast in this case.
chunks.push(String.fromCharCode.apply(null, pending.subarray(0, pendingIndex)));
if (!more) {
return chunks.join('');
}
// Move the buffer forward and create another chunk.
bytes = bytes.subarray(inputIndex);
inputIndex = 0;
pendingIndex = 0;
}
// The native TextDecoder will generate "REPLACEMENT CHARACTER" where the
// input data is invalid. Here, we blindly parse the data even if it's
// wrong: e.g., if a 3-byte sequence doesn't have two valid continuations.
const byte1 = bytes[inputIndex++];
if ((byte1 & 0x80) === 0) { // 1-byte or null
pending[pendingIndex++] = byte1;
} else if ((byte1 & 0xe0) === 0xc0) { // 2-byte
const byte2 = bytes[inputIndex++] & 0x3f;
pending[pendingIndex++] = ((byte1 & 0x1f) << 6) | byte2;
} else if ((byte1 & 0xf0) === 0xe0) { // 3-byte
const byte2 = bytes[inputIndex++] & 0x3f;
const byte3 = bytes[inputIndex++] & 0x3f;
pending[pendingIndex++] = ((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3;
} else if ((byte1 & 0xf8) === 0xf0) { // 4-byte
const byte2 = bytes[inputIndex++] & 0x3f;
const byte3 = bytes[inputIndex++] & 0x3f;
const byte4 = bytes[inputIndex++] & 0x3f;
// this can be > 0xffff, so possibly generate surrogates
let codepoint = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4;
if (codepoint > 0xffff) {
// codepoint &= ~0x10000;
codepoint -= 0x10000;
pending[pendingIndex++] = (codepoint >>> 10) & 0x3ff | 0xd800;
codepoint = 0xdc00 | codepoint & 0x3ff;
}
pending[pendingIndex++] = codepoint;
} else {
// invalid initial byte
}
}
}
// Decoding a string is pretty slow, but use alternative options where possible.
let decodeImpl = decodeFallback;
if (typeof Buffer === 'function' && Buffer.from) {
// Buffer.from was added in Node v5.10.0 (2015-11-17).
decodeImpl = decodeBuffer;
} else if (typeof Blob === 'function' && typeof URL === 'function' && typeof URL.createObjectURL === 'function') {
// Blob and URL.createObjectURL are available from IE10, Safari 6, Chrome 19
// (all released in 2012), Firefox 19 (2013), ...
decodeImpl = decodeSyncXHR;
}
/**
* @param {(!ArrayBuffer|!ArrayBufferView)} buffer
* @param {{stream: boolean}=} options
* @return {string}
*/
FastTextDecoder.prototype['decode'] = function(buffer, options={stream: false}) {
if (options['stream']) {
throw new Error(`Failed to decode: the 'stream' option is unsupported.`);
}
if (!buffer)
return;
let bytes;
if (buffer instanceof Uint8Array) {
// Accept Uint8Array instances as-is.
bytes = buffer;
} else if (buffer.buffer instanceof ArrayBuffer) {
// Look for ArrayBufferView, which isn't a real type, but basically
// represents all the valid TypedArray types plus DataView. They all have
// ".buffer" as an instance of ArrayBuffer.
bytes = new Uint8Array(buffer.buffer);
} else {
// The only other valid argument here is that "buffer" is an ArrayBuffer.
// We also try to convert anything else passed to a Uint8Array, as this
// catches anything that's array-like. Native code would throw here.
bytes = new Uint8Array(buffer);
}
return decodeImpl(/** @type {!Uint8Array} */ (bytes));
}
scope['TextEncoder'] = FastTextEncoder;
scope['TextDecoder'] = FastTextDecoder;
}(typeof window !== 'undefined' ? window : (typeof global !== 'undefined' ? global : this)));

View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1 @@
See https://github.com/samthor/fast-text-encoding.

View file

@ -23,8 +23,35 @@
* THE POSSIBILITY OF SUCH DAMAGE.
*/
class Benchmark {
runIteration() {
__web_tooling_global_suites__.chai.fn();
}
const isInBrowser = false;
if (typeof console == "undefined")
console = {};
console.debug ??= (...args) => console.log("Debug:", ...args);
console.log ??= (...args) => print(args.join(" "));
console.warn ??= (...args) => console.log("Warn:", ...args);
console.error ??= (...args) => console.log("Error:", ...args);
console.assert ??= (condition, message) => {
if (!condition)
throw new Error(`Assertion failed: ${message}`);
};
console.trace ??= () => {
const targetObject = {};
Error.captureStackTrace(targetObject);
console.log(targetObject.stack);
};
const isD8 = typeof Realm !== "undefined";
if (isD8)
globalThis.readFile = read;
const isSpiderMonkey = typeof newGlobal !== "undefined";
if (isSpiderMonkey) {
globalThis.readFile = readRelativeToScript;
globalThis.arguments = scriptArgs;
}
if (typeof performance == "undefined")
performance = {};
performance.mark ??= function(){};
performance.measure ??= function(){};

View file

@ -11,28 +11,6 @@
// Empty `URLSearchParams` has just the same interface as a `Map`.
globalThis.URLSearchParams = Map;
// `TextEncoder` and `TextDecoder`. These are called only a few times with short
// ASCII strings, so this is sufficient and not performance-critical.
class TextEncoder {
encode(string) {
return Uint8Array.from(string, (char) => {
let byte = char.codePointAt(0);
if (byte > 0x7f)
throw new Error("TextEncoder polyfill only supports ASCII");
return byte;
});
}
}
class TextDecoder {
decode(array) {
for (let byte of array) {
if (byte > 0x7f)
throw new Error("TextDecoder polyfill only supports ASCII");
}
return String.fromCharCode.apply(null, array);
}
}
// Now, some configuration options for when we initialize SQLite.
// Use JetStream functions instead of `console.log` and friends.
@ -53,7 +31,7 @@ class Benchmark {
sqlite3Module;
async init() {
Module.wasmBinary = await getBinary(wasmBinary);
Module.wasmBinary = await JetStream.getBinary(JetStream.preload.wasmBinary);
}
async runIteration() {

View file

@ -0,0 +1,27 @@
// Babel plugin that adds CACHE_BUST_COMMENT to every function body.
const CACHE_BUST_COMMENT = "ThouShaltNotCache";
export default function({ types: t }) {
return {
visitor: {
Function(path) {
const bodyPath = path.get("body");
// Handle arrow functions: () => "value"
// Convert them to block statements: () => { return "value"; }
if (!bodyPath.isBlockStatement()) {
const newBody = t.blockStatement([t.returnStatement(bodyPath.node)]);
path.set("body", newBody);
}
// Handle empty function bodies: function foo() {}
// Add an empty statement so we have a first node to attach the comment to.
if (path.get("body.body").length === 0) {
path.get("body").pushContainer("body", t.emptyStatement());
}
const firstNode = path.node.body.body[0];
t.addComment(firstNode, "leading", CACHE_BUST_COMMENT);
},
},
};
};

View file

@ -0,0 +1,128 @@
const CACHE_BUST_COMMENT = "/*ThouShaltNotCache*/";
const CACHE_BUST_COMMENT_RE = new RegExp(
`\n${RegExp.escape(CACHE_BUST_COMMENT)}\n`,
"g"
);
class StartupBenchmark {
// Total iterations for this benchmark.
#iterationCount = 0;
// Original source code.
#sourceCode;
// quickHahs(this.#sourceCode) for use in custom validate() methods.
#sourceHash = 0;
// Number of no-cache comments in the original #sourceCode.
#expectedCacheCommentCount = 0;
// How many times (separate iterations) should we reuse the source code.
// Use 0 to skip and only use a single #sourceCode string.
#sourceCodeReuseCount = 1;
// #sourceCode for each iteration, number of unique sources is controlled
// by codeReuseCount;
#iterationSourceCodes = [];
constructor({
iterationCount,
expectedCacheCommentCount,
sourceCodeReuseCount = 1,
} = {}) {
console.assert(
iterationCount > 0,
`Expected iterationCount to be positive, but got ${iterationCount}`
);
this.#iterationCount = iterationCount;
console.assert(
expectedCacheCommentCount > 0,
`Expected expectedCacheCommentCount to be positive, but got ${expectedCacheCommentCount}`
);
this.#expectedCacheCommentCount = expectedCacheCommentCount;
console.assert(
sourceCodeReuseCount >= 0,
`Expected sourceCodeReuseCount to be non-negative, but got ${sourceCodeReuseCount}`
);
this.#sourceCodeReuseCount = sourceCodeReuseCount;
}
get iterationCount() {
return this.#iterationCount;
}
get sourceCode() {
return this.#sourceCode;
}
get sourceHash() {
return this.#sourceHash;
}
get expectedCacheCommentCount() {
return this.#expectedCacheCommentCount;
}
get sourceCodeReuseCount() {
return this.#sourceCodeReuseCount;
}
get iterationSourceCodes() {
return this.#iterationSourceCodes;
}
async init() {
this.#sourceCode = await JetStream.getString(JetStream.preload.BUNDLE);
const cacheCommentCount = this.sourceCode.match(
CACHE_BUST_COMMENT_RE
).length;
this.#sourceHash = this.quickHash(this.sourceCode);
this.validateSourceCacheComments(cacheCommentCount);
for (let i = 0; i < this.iterationCount; i++)
this.#iterationSourceCodes[i] = this.createIterationSourceCode(i);
this.validateIterationSourceCodes();
}
validateSourceCacheComments(cacheCommentCount) {
console.assert(
cacheCommentCount === this.expectedCacheCommentCount,
`Invalid cache comment count ${cacheCommentCount} expected ${this.expectedCacheCommentCount}.`
);
}
validateIterationSourceCodes() {
if (this.#iterationSourceCodes.some((each) => !each?.length))
throw new Error(`Got invalid iterationSourceCodes`);
let expectedSize = 1;
if (this.sourceCodeReuseCount !== 0)
expectedSize = Math.ceil(this.iterationCount / this.sourceCodeReuseCount);
const uniqueSources = new Set(this.iterationSourceCodes);
if (uniqueSources.size != expectedSize)
throw new Error(
`Expected ${expectedSize} unique sources, but got ${uniqueSources.size}.`
);
}
createIterationSourceCode(iteration) {
// Alter the code per iteration to prevent caching.
const cacheId =
Math.floor(iteration / this.sourceCodeReuseCount) *
this.sourceCodeReuseCount;
// Reuse existing sources if this.codeReuseCount > 1:
if (cacheId < this.iterationSourceCodes.length)
return this.iterationSourceCodes[cacheId];
const sourceCode = this.sourceCode.replaceAll(
CACHE_BUST_COMMENT_RE,
`/*${cacheId}*/`
);
// Warm up quickHash.
this.quickHash(sourceCode);
return sourceCode;
}
quickHash(str) {
let hash = 5381;
let i = str.length;
while (i > 0) {
hash = (hash * 33) ^ (str.charCodeAt(i) | 0);
i -= 919;
}
return hash | 0;
}
}

82
tests/helper.mjs Normal file
View file

@ -0,0 +1,82 @@
import { styleText } from "node:util";
import core from "@actions/core";
import commandLineUsage from "command-line-usage";
export const GITHUB_ACTIONS_OUTPUT = "GITHUB_ACTIONS_OUTPUT" in process.env;
export function logInfo(...args) {
const text = args.join(" ")
if (GITHUB_ACTIONS_OUTPUT)
core.info(styleText("yellow", text));
else
console.log(styleText("yellow", text));
}
export function logError(...args) {
let error;
if (args.length == 1 && args[0] instanceof Error)
error = args[0];
const text = args.join(" ");
if (GITHUB_ACTIONS_OUTPUT) {
if (error?.stack)
core.error(error.stack);
else
core.error(styleText("red", text));
} else {
if (error?.stack)
console.error(styleText("red", error.stack));
else
console.error(styleText("red", text));
}
}
export async function logGroup(name, body) {
if (GITHUB_ACTIONS_OUTPUT) {
core.startGroup(name);
} else {
logInfo("=".repeat(80));
logInfo(name);
logInfo(".".repeat(80));
}
try {
return await body();
} finally {
if (GITHUB_ACTIONS_OUTPUT)
core.endGroup();
}
}
export function printHelp(message = "", optionDefinitions) {
const usage = commandLineUsage([
{
header: "Run all tests",
},
{
header: "Options",
optionList: optionDefinitions,
},
]);
if (!message) {
console.log(usage);
process.exit(0);
} else {
console.error(message);
console.error();
console.error(usage);
process.exit(1);
}
}
export async function runTest(label, testFunction) {
try {
await logGroup(label, testFunction);
logInfo("✅ Test completed!");
} catch(e) {
logError("❌ Test failed!");
logError(e);
return false;
}
return true;
}

165
tests/run-browser.mjs Normal file
View file

@ -0,0 +1,165 @@
#! /usr/bin/env node
/* eslint-disable-next-line no-unused-vars */
import serve from "./server.mjs";
import { Builder, Capabilities } from "selenium-webdriver";
import commandLineArgs from "command-line-args";
import {logInfo, logError, printHelp, runTest} from "./helper.mjs";
const optionDefinitions = [
{ name: "browser", type: String, description: "Set the browser to test, choices are [safari, firefox, chrome, edge]. By default the $BROWSER env variable is used." },
{ name: "port", type: Number, defaultValue: 8010, description: "Set the test-server port, The default value is 8010." },
{ name: "help", alias: "h", description: "Print this help text." },
];
const options = commandLineArgs(optionDefinitions);
if ("help" in options)
printHelp(optionDefinitions);
const BROWSER = options?.browser;
if (!BROWSER)
printHelp("No browser specified, use $BROWSER or --browser", optionDefinitions);
let capabilities;
switch (BROWSER) {
case "safari":
capabilities = Capabilities.safari();
break;
case "firefox": {
capabilities = Capabilities.firefox();
break;
}
case "chrome": {
capabilities = Capabilities.chrome();
break;
}
case "edge": {
capabilities = Capabilities.edge();
break;
}
default: {
printHelp(`Invalid browser "${BROWSER}", choices are: "safari", "firefox", "chrome", "edge"`);
}
}
process.on("unhandledRejection", (err) => {
logError(err);
process.exit(1);
});
process.once("uncaughtException", (err) => {
logError(err);
process.exit(1);
});
const PORT = options.port;
const server = await serve(PORT);
async function runTests() {
let success = true;
try {
success &&= await runEnd2EndTest("Run Single Suite", { test: "proxy-mobx" });
success &&= await runEnd2EndTest("Run Tag No Prefetch", { tag: "proxy", prefetchResources: "false" });
success &&= await runEnd2EndTest("Run Disabled Suite", { tag: "disabled" });
success &&= await runEnd2EndTest("Run Default Suite");
} finally {
server.close();
}
if (!success)
process.exit(1);
}
async function runEnd2EndTest(name, params) {
return runTest(name, () => testEnd2End(params));
}
async function testEnd2End(params) {
const driver = await new Builder().withCapabilities(capabilities).build();
const driverCapabilities = await driver.getCapabilities();
logInfo(`Browser: ${driverCapabilities.getBrowserName()} ${driverCapabilities.getBrowserVersion()}`);
const urlParams = Object.assign({
worstCaseCount: 2,
iterationCount: 3
}, params);
let results;
try {
const url = new URL(`http://localhost:${PORT}/index.html`);
url.search = new URLSearchParams(urlParams).toString();
logInfo(`JetStream PREPARE ${url}`);
await driver.get(url.toString());
await driver.executeAsyncScript((callback) => {
// callback() is explicitly called without the default event
// as argument to avoid serialization issues with chromedriver.
globalThis.addEventListener("JetStreamReady", () => callback());
// We might not get a chance to install the on-ready listener, thus
// we also check if the runner is ready synchronously.
if (globalThis?.JetStream?.isReady)
callback();
});
results = await benchmarkResults(driver);
// FIXME: validate results;
} catch(e) {
throw e;
} finally {
driver.quit();
}
}
async function benchmarkResults(driver) {
logInfo("JetStream START");
await driver.manage().setTimeouts({ script: 2 * 60_000 });
await driver.executeAsyncScript((callback) => {
globalThis.JetStream.start();
callback();
});
await new Promise((resolve, reject) => pollResultsUntilDone(driver, resolve, reject));
const resultsJSON = await driver.executeScript(() => {
return globalThis.JetStream.resultsJSON();
});
return JSON.parse(resultsJSON);
}
class JetStreamTestError extends Error {
constructor(errors) {
super(`Tests failed: ${errors.map(e => e.stack).join(", ")}`);
this.errors = errors;
}
}
const UPDATE_INTERVAL = 250;
async function pollResultsUntilDone(driver, resolve, reject) {
const previousResults = new Set();
const intervalId = setInterval(async function logResult() {
const {done, errors, resultsJSON} = await driver.executeScript(() => {
return {
done: globalThis.JetStream.isDone,
errors: globalThis.JetStream.errors,
resultsJSON: JSON.stringify(globalThis.JetStream.resultsObject("simple")),
};
});
if (errors.length) {
clearInterval(intervalId);
reject(new JetStreamTestError(errors));
}
logIncrementalResult(previousResults, JSON.parse(resultsJSON));
if (done) {
clearInterval(intervalId);
resolve();
}
}, UPDATE_INTERVAL)
}
function logIncrementalResult(previousResults, benchmarkResults) {
for (const [testName, testResults] of Object.entries(benchmarkResults)) {
if (previousResults.has(testName))
continue;
console.log(testName, testResults);
previousResults.add(testName);
}
}
setImmediate(runTests);

View file

@ -1,49 +1,29 @@
#! /usr/bin/env node
import commandLineArgs from "command-line-args";
import commandLineUsage from "command-line-usage";
import { spawnSync } from "child_process";
import { spawn } from "child_process";
import { fileURLToPath } from "url";
import { styleText } from "node:util";
import * as path from "path";
import * as fs from "fs";
import * as os from "os";
import core from "@actions/core"
import core from "@actions/core";
import {logInfo, logError, logGroup, printHelp, runTest, GITHUB_ACTIONS_OUTPUT} from "./helper.mjs";
const optionDefinitions = [
{ name: "shell", type: String, description: "Set the shell to test, choices are [jsc, v8, spidermonkey]." },
{ name: "help", alias: "h", description: "Print this help text." },
];
function printHelp(message = "") {
const usage = commandLineUsage([
{
header: "Run all tests",
},
{
header: "Options",
optionList: optionDefinitions,
},
]);
if (!message) {
console.log(usage);
process.exit(0);
} else {
console.error(message);
console.error();
console.error(usage);
process.exit(1);
}
}
const options = commandLineArgs(optionDefinitions);
if ("help" in options)
printHelp();
printHelp(optionDefinitions);
const JS_SHELL= options?.shell;
if (!JS_SHELL)
printHelp("No javascript shell specified, use --shell");
printHelp("No javascript shell specified, use --shell", optionDefinitions);
const SHELL_NAME = (function() {
switch (JS_SHELL) {
@ -66,51 +46,20 @@ const SHELL_NAME = (function() {
const FILE_PATH = fileURLToPath(import.meta.url);
const SRC_DIR = path.dirname(path.dirname(FILE_PATH));
const CLI_PATH = path.join(SRC_DIR, "cli.js");
const UNIT_TEST_PATH = path.join(SRC_DIR, "tests", "unit-tests.js");
const BASE_CLI_ARGS_WITH_OPTIONS = [CLI_PATH];
if (SHELL_NAME != "spidermonkey")
BASE_CLI_ARGS_WITH_OPTIONS.push("--");
Object.freeze(BASE_CLI_ARGS_WITH_OPTIONS);
const GITHUB_ACTIONS_OUTPUT = "GITHUB_ACTIONS_OUTPUT" in process.env;
function log(...args) {
const text = args.join(" ")
if (GITHUB_ACTIONS_OUTPUT)
core.info(styleText("yellow", text))
else
console.log(styleText("yellow", text))
function convertCliArgs(cli, ...cliArgs) {
if (SHELL_NAME == "spidermonkey")
return [cli, ...cliArgs];
return [cli, "--", ...cliArgs];
}
function logError(...args) {
const text = args.join(" ")
if (GITHUB_ACTIONS_OUTPUT)
core.error(styleText("red", text))
else
console.error(styleText("red", text))
}
function logGroup(name, body) {
if (GITHUB_ACTIONS_OUTPUT) {
core.startGroup(name);
} else {
log("=".repeat(80))
log(name);
log(".".repeat(80))
}
try {
return body();
} finally {
if (GITHUB_ACTIONS_OUTPUT)
core.endGroup();
}
}
const SPAWN_OPTIONS = {
stdio: ["inherit", "inherit", "inherit"]
};
function sh(binary, args) {
async function sh(binary, ...args) {
const cmd = `${binary} ${args.join(" ")}`;
if (GITHUB_ACTIONS_OUTPUT) {
core.startGroup(binary);
@ -119,28 +68,52 @@ function sh(binary, args) {
console.log(styleText("blue", cmd));
}
try {
const result = spawnSync(binary, args, SPAWN_OPTIONS);
const result = await spawnCaptureStdout(binary, args, SPAWN_OPTIONS);
if (result.status || result.error) {
logError(result.error);
throw new Error(`Shell CMD failed: ${binary} ${args.join(" ")}`);
}
return result;
} finally {
if (GITHUB_ACTIONS_OUTPUT)
core.endGroup()
core.endGroup();
}
}
async function runTests() {
const shellBinary = logGroup(`Installing JavaScript Shell: ${SHELL_NAME}`, testSetup);
let success = true;
success &&= runTest("Run Complete Suite", () => sh(shellBinary, [CLI_PATH]));
success &&= runTest("Run Single Suite", () => {
const singleTestArgs = [...BASE_CLI_ARGS_WITH_OPTIONS, "proxy-mobx"];
sh(shellBinary, singleTestArgs);
async function spawnCaptureStdout(binary, args) {
const childProcess = spawn(binary, args);
childProcess.stdout.pipe(process.stdout);
return new Promise((resolve, reject) => {
childProcess.stdoutString = "";
childProcess.stdio[1].on("data", (data) => {
childProcess.stdoutString += data.toString();
});
if (!success) {
process.exit(1)
}
childProcess.on('close', (code) => {
if (code === 0) {
resolve(childProcess);
} else {
// Reject the Promise with an Error on failure
const error = new Error(`Command failed with exit code ${code}: ${binary} ${args.join(" ")}`);
error.process = childProcess;
error.stdout = childProcess.stdoutString;
error.exitCode = code;
reject(error);
}
});
childProcess.on('error', reject);
})
}
async function runTests() {
const shellBinary = await logGroup(`Installing JavaScript Shell: ${SHELL_NAME}`, testSetup);
let success = true;
success &&= await runTest("Run UnitTests", () => sh(shellBinary, UNIT_TEST_PATH));
success &&= await runCLITest("Run Single Suite", shellBinary, "proxy-mobx");
success &&= await runCLITest("Run Tag No Prefetch", shellBinary, "proxy", "--no-prefetch");
success &&= await runCLITest("Run Disabled Suite", shellBinary, "disabled");
success &&= await runCLITest("Run Default Suite", shellBinary);
if (!success)
process.exit(1);
}
function jsvuOSName() {
@ -159,30 +132,30 @@ function jsvuOSName() {
default: throw new Error("Unsupported architecture");
}
};
return `${osName()}${osArch()}`
return `${osName()}${osArch()}`;
}
const DEFAULT_JSC_LOCATION = "/System/Library/Frameworks/JavaScriptCore.framework/Versions/Current/Helpers/jsc"
function testSetup() {
sh("jsvu", [`--engines=${SHELL_NAME}`, `--os=${jsvuOSName()}`]);
async function testSetup() {
await sh("jsvu", `--engines=${SHELL_NAME}`, `--os=${jsvuOSName()}`);
let shellBinary = path.join(os.homedir(), ".jsvu/bin", SHELL_NAME);
if (!fs.existsSync(shellBinary) && SHELL_NAME == "javascriptcore")
shellBinary = DEFAULT_JSC_LOCATION
shellBinary = DEFAULT_JSC_LOCATION;
if (!fs.existsSync(shellBinary))
throw new Error(`Could not find shell binary: ${shellBinary}`);
log(`Installed JavaScript Shell: ${shellBinary}`);
return shellBinary
logInfo(`Installed JavaScript Shell: ${shellBinary}`);
return shellBinary;
}
function runTest(testName, test) {
try {
logGroup(testName, test)
} catch(e) {
logError("TEST FAILED")
return false
}
return true
function runCLITest(name, shellBinary, ...args) {
return runTest(name, () => runShell(shellBinary, ...convertCliArgs(CLI_PATH, ...args)));
}
async function runShell(shellBinary, ...args) {
const result = await sh(shellBinary, ...args);
if (result.stdoutString.includes("JetStream3 failed"))
throw new Error("test failed")
}
setImmediate(runTests);

260
tests/unit-tests.js Normal file
View file

@ -0,0 +1,260 @@
load("shell-config.js");
load("startup-helper/StartupBenchmark.js");
load("JetStreamDriver.js");
function assertTrue(condition, message) {
if (!condition) {
throw new Error(message || "Assertion failed");
}
}
function assertFalse(condition, message) {
if (condition) {
throw new Error(message || "Assertion failed");
}
}
function assertEquals(actual, expected, message) {
if (actual !== expected) {
throw new Error(message || `Expected ${expected}, but got ${actual}`);
}
}
function assertThrows(message, func) {
let didThrow = false;
try {
func();
} catch (e) {
didThrow = true;
}
assertTrue(didThrow, message);
}
(function testTagsAreLowerCaseStrings() {
for (const benchmark of BENCHMARKS) {
benchmark.tags.forEach((tag) => {
assertTrue(typeof tag == "string");
assertTrue(tag == tag.toLowerCase());
});
}
})();
(function testTagsAll() {
for (const benchmark of BENCHMARKS) {
const tags = benchmark.tags;
assertTrue(tags instanceof Set);
assertTrue(tags.size > 0);
assertTrue(tags.has("all"));
assertFalse(tags.has("All"));
assertTrue(tags.has("default") ^ tags.has("disabled"));
}
})();
(function testDriverBenchmarksOrder() {
const benchmarks = findBenchmarksByTag("all");
const driver = new Driver(benchmarks);
assertEquals(driver.benchmarks.length, BENCHMARKS.length);
const names = driver.benchmarks
.map((b) => b.name.toLowerCase())
.sort()
.reverse();
for (let i = 0; i < names.length; i++) {
assertEquals(driver.benchmarks[i].name.toLowerCase(), names[i]);
}
})();
(function testEnableByTag() {
const driverA = new Driver(findBenchmarksByTag("Default"));
const driverB = new Driver(findBenchmarksByTag("default"));
assertTrue(driverA.benchmarks.length > 0);
assertEquals(driverA.benchmarks.length, driverB.benchmarks.length);
const enabledBenchmarkNames = new Set(
Array.from(driverA.benchmarks).map((b) => b.name)
);
for (const benchmark of BENCHMARKS) {
if (benchmark.tags.has("default"))
assertTrue(enabledBenchmarkNames.has(benchmark.name));
}
})();
(function testDriverEnableDuplicateAndSort() {
const benchmarks = [
...findBenchmarksByTag("wasm"),
...findBenchmarksByTag("wasm"),
];
assertTrue(benchmarks.length > 0);
const uniqueBenchmarks = new Set(benchmarks);
assertFalse(uniqueBenchmarks.size == benchmarks.length);
const driver = new Driver(benchmarks);
assertEquals(driver.benchmarks.length, uniqueBenchmarks.size);
})();
(function testBenchmarkSubScores() {
for (const benchmark of BENCHMARKS) {
const subScores = benchmark.subScores();
assertTrue(subScores instanceof Object);
assertTrue(Object.keys(subScores).length > 0);
for (const [name, value] of Object.entries(subScores)) {
assertTrue(typeof name == "string");
// "Score" can only be part of allScores().
assertFalse(name == "Score");
// Without running values should be either null (or 0 for GroupedBenchmark)
assertFalse(value);
}
}
})();
(function testBenchmarkAllScores() {
for (const benchmark of BENCHMARKS) {
const subScores = benchmark.subScores();
const allScores = benchmark.allScores();
assertTrue("Score" in allScores);
// All subScore items are part of allScores.
for (const name of Object.keys(subScores)) assertTrue(name in allScores);
}
})();
(function checkUtf16Sources() {
// Test that only explicitly UTF16-enabled benchmarks can have sources
// with non-8-byte characters.
const twoByteCharsRegex = /[^\x00-\xFF]/g;
const jsFileRegex = /\.(js|mjs)$/;
function checkFile(benchmarkName, file, type) {
if (!jsFileRegex.test(file))
return;
const content = read(file);
const match = content.match(twoByteCharsRegex);
if (!match)
return;
const uniqueMatches = Array.from(new Set(match));
const offendingChars = uniqueMatches.map(char => {
const hex = char.charCodeAt(0).toString(16).padStart(4, "0");
return `\n - \\u${hex}: '${char}'`;
}).join("");
throw new Error(
`Benchmark '${benchmarkName}' has two-byte characters in ${type} '${file}':\n` +
` Offending characters: ${offendingChars}`);
}
for (const benchmark of BENCHMARKS) {
if (benchmark.allowUtf16)
continue;
for (const file of benchmark.files) {
checkFile(benchmark.name, file, "file");
}
if (benchmark.plan.preload) {
for (const [name, file] of Object.entries(benchmark.plan.preload)) {
checkFile(benchmark.name, file, `preload.${name}`);
}
}
}
})();
function validateIterationSources(sources) {
for (const source of sources) {
assertTrue(typeof source == "string");
assertFalse(source.includes(CACHE_BUST_COMMENT));
}
}
(async function testStartupBenchmark() {
try {
JetStream.preload = { BUNDLE: "test-bundle.js" };
JetStream.getString = (file) => {
assertEquals(file, "test-bundle.js");
return `function test() {
${CACHE_BUST_COMMENT}
return 1;
}`;
};
await testStartupBenchmarkInnerTests();
} finally {
JetStream.preload = undefined;
JetStream.getString = undefined;
}
})();
async function testStartupBenchmarkInnerTests() {
const benchmark = new StartupBenchmark({
iterationCount: 12,
expectedCacheCommentCount: 1,
});
assertEquals(benchmark.iterationCount, 12);
assertEquals(benchmark.expectedCacheCommentCount, 1);
assertEquals(benchmark.iterationSourceCodes.length, 0);
assertEquals(benchmark.sourceCode, undefined);
assertEquals(benchmark.sourceHash, 0);
await benchmark.init();
assertEquals(benchmark.sourceHash, 177573);
assertEquals(benchmark.sourceCode.length, 68);
assertEquals(benchmark.iterationSourceCodes.length, 12);
assertEquals(new Set(benchmark.iterationSourceCodes).size, 12);
validateIterationSources(benchmark.iterationSourceCodes);
const noReuseBenchmark = new StartupBenchmark({
iterationCount: 12,
expectedCacheCommentCount: 1,
sourceCodeReuseCount: 0,
});
assertEquals(noReuseBenchmark.iterationSourceCodes.length, 0);
await noReuseBenchmark.init();
assertEquals(noReuseBenchmark.iterationSourceCodes.length, 12);
assertEquals(new Set(noReuseBenchmark.iterationSourceCodes).size, 1);
validateIterationSources(noReuseBenchmark.iterationSourceCodes);
const reuseBenchmark = new StartupBenchmark({
iterationCount: 12,
expectedCacheCommentCount: 1,
sourceCodeReuseCount: 3,
});
assertEquals(reuseBenchmark.iterationSourceCodes.length, 0);
await reuseBenchmark.init();
assertEquals(reuseBenchmark.iterationSourceCodes.length, 12);
assertEquals(new Set(reuseBenchmark.iterationSourceCodes).size, 4);
validateIterationSources(reuseBenchmark.iterationSourceCodes);
const reuseBenchmark2 = new StartupBenchmark({
iterationCount: 12,
expectedCacheCommentCount: 1,
sourceCodeReuseCount: 5,
});
assertEquals(reuseBenchmark2.iterationSourceCodes.length, 0);
await reuseBenchmark2.init();
assertEquals(reuseBenchmark2.iterationSourceCodes.length, 12);
assertEquals(new Set(reuseBenchmark2.iterationSourceCodes).size, 3);
validateIterationSources(reuseBenchmark2.iterationSourceCodes);
}
(function testStartupBenchmarkThrow() {
assertThrows(
"StartupBenchmark constructor should throw with no arguments.",
() => new StartupBenchmark()
);
assertThrows(
"StartupBenchmark constructor should throw with missing expectedCacheCommentCount.",
() => new StartupBenchmark({ iterationCount: 1 })
);
assertThrows(
"StartupBenchmark constructor should throw with missing iterationCount.",
() => new StartupBenchmark({ expectedCacheCommentCount: 1 })
);
assertThrows(
"StartupBenchmark constructor should throw with iterationCount=0.",
() => {
new StartupBenchmark({ iterationCount: 0, expectedCacheCommentCount: 1 });
}
);
assertThrows(
"StartupBenchmark constructor should throw with expectedCacheCommentCount=0.",
() => {
new StartupBenchmark({ iterationCount: 1, expectedCacheCommentCount: 0 });
}
);
})();

224
threejs/benchmark.js Normal file
View file

@ -0,0 +1,224 @@
/*
* Copyright (C) 2025 Mozilla. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
const NUM_PARTICLES = 8000;
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111111);
const camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);
const innerHeight = 1080;
const innerWidth = 1920;
camera.position.z = 500;
const canvas = {
addEventListener() {},
style: {},
getContext(kind) {
return {
getExtension(extension) {
if (extension == 'EXT_blend_minmax') {
return {MIN_EXT: 32775, MAX_EXT: 32776}
}
if (extension == 'OES_vertex_array_object') {
return {
createVertexArrayOES() { return 1 },
bindVertexArrayOES() {},
deleteVertexArrayOES() {},
}
}
},
createTexture() {},
bindTexture() {},
texImage2D() {},
texImage3D() {},
texParameteri() {},
uniform1i() {},
uniform1f() {},
uniform2f() {},
uniform3f() {},
uniform4f() {},
clearColor() {},
clear() {},
clearDepth() {},
clearStencil() {},
enable() {},
disable() {},
depthFunc() {},
depthMask() {},
frontFace() {},
cullFace() {},
getContextAttributes() {},
createBuffer() {},
createFramebuffer() {},
bindBuffer() {},
bufferData() {},
createProgram() {},
attachShader() {},
linkProgram() {},
useProgram() {},
getAttribLocation() {},
getUniformLocation() {},
createShader() {},
shaderSource() {},
compileShader() {},
getShaderParameter() {},
getProgramInfoLog() { return "" },
getShaderInfoLog() { return "" },
getProgramParameter() {},
deleteShader() {},
colorMask() {},
stencilMask() {},
createVertexArray() {},
bindVertexArray() {},
drawElements() {},
lineWidth() {},
drawArrays() {},
viewport() {},
getParameter(param) {
if (param == 34930) { return 16 }
if (param == 35660) { return 16 }
if (param == 3379) { return 8192 }
if (param == 36347) { return 1024 }
if (param == 36348) { return 32 }
if (param == 36349) { return 1024 }
if (param == 35661) { return 80 }
if (param == 7938) { return "WebGL 2.0" }
if (param == 3088) { return [0,0,1024,480] }
if (param == 2978) { return [0,0,1024,480] }
},
MAX_TEXTURE_IMAGE_UNITS: 34930,
MAX_VERTEX_TEXTURE_IMAGE_UNITS: 35660,
MAX_TEXTURE_SIZE: 3379,
MAX_VERTEX_UNIFORM_VECTORS: 36347,
MAX_VARYING_VECTORS: 36348,
MAX_FRAGMENT_UNIFORM_VECTORS: 36349,
MAX_COMBINED_TEXTURE_IMAGE_UNITS: 35661,
VERSION: 7938,
SCISSOR_BOX: 3088,
VIEWPORT: 2978
}
}
}
const renderer = new THREE.WebGLRenderer({
antialias: false,
canvas,
powerPreference: 'low-power',
precision: 'lowp'
});
renderer.setSize(innerWidth, innerHeight);
const createGeometryParticle = (size) => {
const visibleHeight = 2 * Math.tan(75 * Math.PI/360) * 500;
const radius = (size / innerHeight) * visibleHeight / 2;
// Main circle
const geometry = new THREE.CircleGeometry(radius, 32);
const material = new THREE.MeshBasicMaterial({
color: 0xffffff,
depthTest: false
});
const circle = new THREE.Mesh(geometry, material);
const posArray = geometry.attributes.position.array;
const outlineVertices = [];
for (let i = 3; i < posArray.length; i += 3) {
outlineVertices.push(
new THREE.Vector3(
posArray[i],
posArray[i + 1],
posArray[i + 2]
)
);
}
const outlineGeometry = new THREE.BufferGeometry().setFromPoints(outlineVertices);
const outline = new THREE.LineLoop(
outlineGeometry,
new THREE.LineBasicMaterial({ color: 0x000000, depthTest: false })
);
const group = new THREE.Group();
group.add(circle);
group.add(outline);
return group;
};
// Initialize particles
var initialized = false;
const particles = [];
function initialize() {
for(let i = 0; i < NUM_PARTICLES; i++) {
const size = 10 + Math.random() * 80;
const particle = createGeometryParticle(size);
// Random initial position
const visibleWidth = 2 * Math.tan(75 * Math.PI/360) * 500 * camera.aspect;
particle.position.set(
THREE.MathUtils.randFloatSpread(visibleWidth),
THREE.MathUtils.randFloatSpread(visibleWidth/camera.aspect),
0
);
// Velocity storage
particle.velocity = new THREE.Vector2(
(Math.random() - 0.5) * 8,
(Math.random() - 0.5) * 8
);
scene.add(particle);
particles.push(particle);
}
initialized = true;
}
// Metrics and animation
const visibleWidth = 2 * Math.tan(75 * Math.PI/360) * 500 * camera.aspect;
const visibleHeight = visibleWidth / camera.aspect;
function animate() {
particles.forEach(particle => {
particle.position.x += particle.velocity.x;
particle.position.y += particle.velocity.y;
// Boundary checks
if(Math.abs(particle.position.x) > visibleWidth/2)
particle.velocity.x *= -1;
if(Math.abs(particle.position.y) > visibleHeight/2)
particle.velocity.y *= -1;
});
renderer.render(scene, camera);
}
class Benchmark {
runIteration() {
if (!initialized) {
initialize();
}
animate();
}
}

31208
threejs/three.js Normal file

File diff suppressed because one or more lines are too long

157
utils/compress.mjs Normal file
View file

@ -0,0 +1,157 @@
import commandLineArgs from 'command-line-args';
import commandLineUsage from 'command-line-usage';
import { globSync } from 'glob';
import zlib from 'zlib';
import fs from 'fs';
import path from 'path';
function parseCommandLineArgs() {
const optionDefinitions = [
{ name: 'decompress', alias: 'd', type: Boolean, description: 'Decompress files (default: compress).' },
{ name: 'keep', alias: 'k', type: Boolean, description: 'Keep input files after processing (default: delete).' },
{ name: 'help', alias: 'h', type: Boolean, description: 'Print this usage guide.' },
{ name: 'globs', type: String, multiple: true, defaultOption: true, description: 'Glob patterns of files to process.' },
];
const options = commandLineArgs(optionDefinitions);
const isNPM = process.env.npm_config_user_agent !== undefined;
const command = isNPM ? 'npm run compress --' : 'node utils/compress.mjs';
const usage = commandLineUsage([
{
header: 'Usage',
content: `${command} [options] <glob>...`
},
{
header: 'Options',
optionList: optionDefinitions
}
]);
if (options.help) {
console.log(usage);
process.exit(0);
}
if (options.globs === undefined) {
if (options.decompress) {
const defaultGlob = '**/*.z';
console.log(`No input glob pattern given, using default: ${defaultGlob}`);
options.globs = [defaultGlob];
} else {
// For compression, require the user to specify explicit input file patterns.
console.error('No input glob pattern given.');
console.log(usage);
process.exit(1);
}
}
return options;
}
function calculateCompressionRatio(originalSize, compressedSize) {
return (1 - compressedSize / originalSize) * 100;
}
function calculateExpansionRatio(compressedSize, decompressedSize) {
return (decompressedSize / compressedSize - 1) * 100;
}
function compress(inputData) {
const compressedData = zlib.deflateSync(inputData, { level: zlib.constants.Z_BEST_COMPRESSION });
const originalSize = inputData.length;
const compressedSize = compressedData.length;
const compressionRatio = calculateCompressionRatio(originalSize, compressedSize);
console.log(` Original size: ${String(originalSize).padStart(8)} bytes`);
console.log(` Compressed size: ${String(compressedSize).padStart(8)} bytes`);
console.log(` Compression ratio: ${compressionRatio.toFixed(2).padStart(8)}%`);
return compressedData;
}
function decompress(inputData) {
const decompressedData = zlib.inflateSync(inputData);
const compressedSize = inputData.length;
const decompressedSize = decompressedData.length;
const expansionRatio = calculateExpansionRatio(compressedSize, decompressedSize);
console.log(` Compressed size: ${String(compressedSize).padStart(8)} bytes`);
console.log(` Decompressed size: ${String(decompressedSize).padStart(8)} bytes`);
console.log(` Expansion ratio: ${expansionRatio.toFixed(2).padStart(8)}%`);
return decompressedData;
}
function globsToFiles(globs) {
let files = [];
console.assert(globs.length > 0);
for (const glob of globs) {
const matches = globSync(glob, { nodir: true });
files = files.concat(matches);
}
files = Array.from(new Set(files)).sort();
return files;
}
function processFiles(files, isDecompress, keep) {
const verb = isDecompress ? 'decompress' : 'compress';
console.log(`Found ${files.length} files to ${verb}` + (files.length ? ':' : '.'));
// For printing overall statistics at the end.
let totalInputSize = 0;
let totalOutputSize = 0;
for (const inputFilename of files) {
try {
console.log(inputFilename);
let outputFilename;
if (isDecompress) {
if (path.extname(inputFilename) !== '.z') {
console.warn(` Warning: Input file does not have a .z extension.`);
outputFilename = `${inputFilename}.decompressed`;
} else {
outputFilename = inputFilename.slice(0, -2);
}
console.log(` Decompressing to: ${outputFilename}`);
} else {
if (path.extname(inputFilename) === '.z') {
console.warn(` Warning: Input file already has a .z extension.`);
}
outputFilename = `${inputFilename}.z`;
}
// Copy the mode over to avoid git status entries after a roundtrip.
const { mode } = fs.statSync(inputFilename);
const inputData = fs.readFileSync(inputFilename);
const outputData = isDecompress ? decompress(inputData) : compress(inputData);
fs.writeFileSync(outputFilename, outputData, { mode });
totalInputSize += inputData.length;
totalOutputSize += outputData.length;
if (!keep) {
fs.unlinkSync(inputFilename);
console.log(` Deleted input file.`);
}
} catch (err) {
console.error(`Error ${verb}ing ${inputFilename}:`, err);
}
}
if (files.length > 1) {
if (isDecompress) {
const totalExpansionRatio = calculateExpansionRatio(totalInputSize, totalOutputSize);
console.log(`Total compressed sizes: ${String(totalInputSize).padStart(9)} bytes`);
console.log(`Total decompressed sizes: ${String(totalOutputSize).padStart(9)} bytes`);
console.log(`Average expansion ratio: ${totalExpansionRatio.toFixed(2).padStart(9)}%`);
} else {
const totalCompressionRatio = calculateCompressionRatio(totalInputSize, totalOutputSize);
console.log(`Total original sizes: ${String(totalInputSize).padStart(9)} bytes`);
console.log(`Total compressed sizes: ${String(totalOutputSize).padStart(9)} bytes`);
console.log(`Average compression ratio: ${totalCompressionRatio.toFixed(2).padStart(9)}%`);
}
}
}
const options = parseCommandLineArgs();
const files = globsToFiles(options.globs);
processFiles(files, options.decompress, options.keep);

View file

@ -1,42 +0,0 @@
/*
* Copyright (C) 2020 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
testList = [
"HashSet-wasm",
"tsf-wasm",
"quicksort-wasm",
"gcc-loops-wasm",
"richards-wasm",
"sqlite3-wasm",
"tfjs-wasm",
"tfjs-wasm-simd",
"argon2-wasm",
"8bitbench-wasm",
"Dart-flute-wasm",
"zlib-wasm",
];
// Reuse the full CLI runner, just with the subset of Wasm line items above.
load("./cli.js");

View file

@ -5,7 +5,7 @@
class Benchmark {
async init() {
Module.wasmBinary = await getBinary(wasmBinary);
Module.wasmBinary = await JetStream.getBinary(JetStream.preload.wasmBinary);
}
async runIteration() {

View file

@ -25,7 +25,7 @@
class Benchmark {
async init() {
Module.wasmBinary = await getBinary(wasmBinary);
Module.wasmBinary = await JetStream.getBinary(JetStream.preload.wasmBinary);
}
async runIteration() {

View file

@ -77,7 +77,7 @@ const saltLength = 12;
class Benchmark {
async init() {
Module.wasmBinary = await getBinary(wasmBinary);
Module.wasmBinary = await JetStream.getBinary(JetStream.preload.wasmBinary);
}
async runIteration() {

8
wasm/dotnet/.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
*.binlog
.dotnet
.nuget
bin
obj
blazor.boot.json
web.config
*.staticwebassets.endpoints.json

30
wasm/dotnet/README.md Normal file
View file

@ -0,0 +1,30 @@
# .NET on WebAssembly
Tests [.NET on WebAssembly](https://github.com/dotnet/runtime). This benchmark tests operations
on .NET implementation of String, JSON serialization, specifics of .NET exceptions and computation
of a 3D scene using Mono Interpreter & AOT. Source code: [.NET](wasm/dotnet)
## Build instructions
Download .NET SDK 9.0.3xx
- [dotnet-sdk-win-x64.zip](https://aka.ms/dotnet/9.0.3xx/daily/dotnet-sdk-win-x64.zip)
- [dotnet-sdk-linux-x64.tar.gz](https://aka.ms/dotnet/9.0.3xx/daily/dotnet-sdk-linux-x64.tar.gz)
Run `build.sh` script. It will install `wasm-tools` workload & build the benchmark code twice (for Mono interpreter & AOT).
To run the benchmark code on `jsc`, we are prepending `import.meta.url ??= ""` to `dotnet.js`.
## Background on .NET / build output files
Mono AOT works in a "mixed mode". It is not able to compile all code patterns and in various scenarios it falls back to interpreter.
Because of that we are still loading managed dlls (all the other not-`dotnet.native.wasm` files).
Structure of the build output
- `dotnet.js` is entrypoint JavaScript with public API.
- `dotnet.runtime.js` is internal implementation of JavaScript logic for .NET.
- `dotnet.native.js` is emscripten module configured for .NET.
- `dotnet.native.wasm` is unmanaged code (Mono runtime + AOT compiled code).
- `System.*.wasm` is .NET BCL that has unused code trimmed away.
- `dotnet.wasm` is the benchmark code.

1
wasm/dotnet/aot.js Normal file
View file

@ -0,0 +1 @@
globalThis.dotnetFlavor = "aot";

138
wasm/dotnet/benchmark.js Normal file
View file

@ -0,0 +1,138 @@
class Benchmark {
async init() {
const config = {
mainAssemblyName: "dotnet.dll",
globalizationMode: "custom",
assets: [
{
name: "dotnet.runtime.js",
resolvedUrl: JetStream.preload.dotnetRuntimeUrl,
moduleExports: await JetStream.dynamicImport(JetStream.preload.dotnetRuntimeUrl),
behavior: "js-module-runtime"
},
{
name: "dotnet.native.js",
resolvedUrl: JetStream.preload.dotnetNativeUrl,
moduleExports: await JetStream.dynamicImport(JetStream.preload.dotnetNativeUrl),
behavior: "js-module-native"
},
{
name: "dotnet.native.wasm",
resolvedUrl: JetStream.preload.wasmBinaryUrl,
buffer: await JetStream.getBinary(JetStream.preload.wasmBinaryUrl),
behavior: "dotnetwasm"
},
{
name: "icudt_CJK.dat",
resolvedUrl: JetStream.preload.icuCustomUrl,
buffer: await JetStream.getBinary(JetStream.preload.icuCustomUrl),
behavior: "icu"
},
{
name: "System.Collections.Concurrent.wasm",
resolvedUrl: JetStream.preload.dllCollectionsConcurrentUrl,
buffer: await JetStream.getBinary(JetStream.preload.dllCollectionsConcurrentUrl),
behavior: "assembly"
},
{
name: "System.Collections.wasm",
resolvedUrl: JetStream.preload.dllCollectionsUrl,
buffer: await JetStream.getBinary(JetStream.preload.dllCollectionsUrl),
behavior: "assembly"
},
{
name: "System.ComponentModel.Primitives.wasm",
resolvedUrl: JetStream.preload.dllComponentModelPrimitivesUrl,
buffer: await JetStream.getBinary(JetStream.preload.dllComponentModelPrimitivesUrl),
behavior: "assembly"
},
{
name: "System.ComponentModel.TypeConverter.wasm",
resolvedUrl: JetStream.preload.dllComponentModelTypeConverterUrl,
buffer: await JetStream.getBinary(JetStream.preload.dllComponentModelTypeConverterUrl),
behavior: "assembly"
},
{
name: "System.Drawing.Primitives.wasm",
resolvedUrl: JetStream.preload.dllDrawingPrimitivesUrl,
buffer: await JetStream.getBinary(JetStream.preload.dllDrawingPrimitivesUrl),
behavior: "assembly"
},
{
name: "System.Drawing.wasm",
resolvedUrl: JetStream.preload.dllDrawingUrl,
buffer: await JetStream.getBinary(JetStream.preload.dllDrawingUrl),
behavior: "assembly"
},
{
name: "System.IO.Pipelines.wasm",
resolvedUrl: JetStream.preload.dllIOPipelinesUrl,
buffer: await JetStream.getBinary(JetStream.preload.dllIOPipelinesUrl),
behavior: "assembly"
},
{
name: "System.Linq.wasm",
resolvedUrl: JetStream.preload.dllLinqUrl,
buffer: await JetStream.getBinary(JetStream.preload.dllLinqUrl),
behavior: "assembly"
},
{
name: "System.Memory.wasm",
resolvedUrl: JetStream.preload.dllMemoryUrl,
buffer: await JetStream.getBinary(JetStream.preload.dllMemoryUrl),
behavior: "assembly"
},
{
name: "System.ObjectModel.wasm",
resolvedUrl: JetStream.preload.dllObjectModelUrl,
buffer: await JetStream.getBinary(JetStream.preload.dllObjectModelUrl),
behavior: "assembly"
},
{
name: "System.Private.CoreLib.wasm",
resolvedUrl: JetStream.preload.dllPrivateCorelibUrl,
buffer: await JetStream.getBinary(JetStream.preload.dllPrivateCorelibUrl),
behavior: "assembly",
isCore: true
},
{
name: "System.Runtime.InteropServices.JavaScript.wasm",
resolvedUrl: JetStream.preload.dllRuntimeInteropServicesJavaScriptUrl,
buffer: await JetStream.getBinary(JetStream.preload.dllRuntimeInteropServicesJavaScriptUrl),
behavior: "assembly",
isCore: true
},
{
name: "System.Text.Encodings.Web.wasm",
resolvedUrl: JetStream.preload.dllTextEncodingsWebUrl,
buffer: await JetStream.getBinary(JetStream.preload.dllTextEncodingsWebUrl),
behavior: "assembly"
},
{
name: "System.Text.Json.wasm",
resolvedUrl: JetStream.preload.dllTextJsonUrl,
buffer: await JetStream.getBinary(JetStream.preload.dllTextJsonUrl),
behavior: "assembly"
},
{
name: "dotnet.wasm",
resolvedUrl: JetStream.preload.dllAppUrl,
buffer: await JetStream.getBinary(JetStream.preload.dllAppUrl),
behavior: "assembly",
isCore: true
}
]
};
this.dotnet = (await JetStream.dynamicImport(JetStream.preload.dotnetUrl)).dotnet;
this.api = await this.dotnet.withModuleConfig({ locateFile: e => e }).withConfig(config).create();
this.exports = await this.api.getAssemblyExports("dotnet.dll");
this.hardwareConcurrency = 1;
this.sceneWidth = dotnetFlavor === "aot" ? 300 : 150;
this.sceneHeight = dotnetFlavor === "aot" ? 200 : 100;
}
async runIteration() {
await this.exports.Interop.RunIteration(this.sceneWidth, this.sceneHeight, this.hardwareConcurrency);
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more