Reset main-custom to the latest main code

Change-Id: Ic89816520b7929e50c673b60a39a44c35fe85996
Reviewed-on: https://chromium-review.googlesource.com/c/external/github.com/WebKit/JetStream/+/6905285
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
This commit is contained in:
Camillo Bruni 2025-09-01 18:00:55 +02:00
parent 5d1737b1b1
commit b0e703ec66
162 changed files with 81968 additions and 5596 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

@ -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
Cloning into 'wasm_gc_benchmarks'...
f80892d Update Dart SDK, switch wasm to -O2, recompile performance benchmarks
Copying files from wasm_gc_benchmarks/ into build/
Build success
Built on Thu Aug 14 04:47:37 PM CEST 2025
Cloning into 'wasm_gc_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.

File diff suppressed because one or more lines are too long

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

@ -1,424 +0,0 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
//
// Runner V8/JSShell script for testing dart2wasm, takes ".wasm" files as
// arguments.
//
// Run as follows on D8:
//
// $> d8 run_wasm.js \
// -- /abs/path/to/<dart_module>.mjs <dart_module>.wasm [<ffi_module>.wasm] \
// [-- Dart commandline arguments...]
//
// Run as follows on JSC:
//
// $> jsc run_wasm.js -- <dart_module>.mjs <dart_module>.wasm [<ffi_module>.wasm] \
// [-- Dart commandline arguments...]
//
// Run as follows on JSShell:
//
// $> js run_wasm.js \
// /abs/path/to/<dart_module>.mjs <dart_module>.wasm [<ffi_module>.wasm] \
// [-- Dart commandline arguments...]
//
// (Notice the missing -- here!)
//
// Please note we require an absolute path for the JS runtime. This is a
// workaround for a discrepancy in D8. Specifically, `import`(used to load .mjs
// files) searches for imports relative to run_wasm.js, but `readbuffer`(used to
// read in .wasm files) searches relative to CWD. A path relative to
// `run_wasm.js` will also work.
//
// Or with the `run_dart2wasm_d8` helper script:
//
// $> sdk/bin/run_dart2wasm_d8 <dart_module>.wasm [<ffi_module>.wasm]
//
// If an FFI module is specified, it will be instantiated first, and its
// exports will be supplied as imports to the Dart module under the 'ffi'
// module name.
const jsRuntimeArg = 0;
const wasmArg = 1;
const ffiArg = 2;
// This script is intended to be used by D8, JSShell or JSC. We distinguish
// them by the functions they offer to read files:
//
// Engine | Shell | FileRead | Arguments
// --------------------------------------------------------------
// V8 | D8 | readbuffer | arguments (arg0 arg1)
// JavaScriptCore | JSC | readFile | arguments (arg0 arg1)
// SpiderMonkey | JSShell | readRelativeToScript | scriptArgs (-- arg0 arg1)
//
const isD8 = (typeof readbuffer === "function");
const isJSC = (typeof readFile === "function");
const isJSShell = (typeof readRelativeToScript === "function");
if (isD8) {
// D8's performance.measure is API incompatible with the browser version.
//
// (see also dart2js's `sdk/**/js_runtime/lib/preambles/d8.js`)
delete performance.measure;
}
function readFileContentsAsBytes(filename) {
var buffer;
if (isJSC) {
buffer = readFile(filename, "binary");
} else if (isD8) {
buffer = readbuffer(filename);
} else {
buffer = readRelativeToScript(filename, "binary");
}
return new Uint8Array(buffer, 0, buffer.byteLength);
}
var args = (isD8 || isJSC) ? arguments : scriptArgs;
var dartArgs = [];
const argsSplit = args.indexOf("--");
if (argsSplit != -1) {
dartArgs = args.slice(argsSplit + 1);
args = args.slice(0, argsSplit);
}
// d8's `setTimeout` doesn't work as expected (it doesn't wait before calling
// the callback), and d8 also doesn't have `setInterval` and `queueMicrotask`.
// So we define our own event loop with these functions.
//
// The code below is copied form dart2js, with some modifications:
// sdk/lib/_internal/js_runtime/lib/preambles/d8.js
(function(self, scriptArguments) {
// Using strict mode to avoid accidentally defining global variables.
"use strict"; // Should be first statement of this function.
// Task queue as cyclic list queue.
var taskQueue = new Array(8); // Length is power of 2.
var head = 0;
var tail = 0;
var mask = taskQueue.length - 1;
function addTask(elem) {
taskQueue[head] = elem;
head = (head + 1) & mask;
if (head == tail) _growTaskQueue();
}
function removeTask() {
if (head == tail) return;
var result = taskQueue[tail];
taskQueue[tail] = undefined;
tail = (tail + 1) & mask;
return result;
}
function _growTaskQueue() {
// head == tail.
var length = taskQueue.length;
var split = head;
taskQueue.length = length * 2;
if (split * 2 < length) { // split < length / 2
for (var i = 0; i < split; i++) {
taskQueue[length + i] = taskQueue[i];
taskQueue[i] = undefined;
}
head += length;
} else {
for (var i = split; i < length; i++) {
taskQueue[length + i] = taskQueue[i];
taskQueue[i] = undefined;
}
tail += length;
}
mask = taskQueue.length - 1;
}
// Mapping from timer id to timer function.
// The timer id is written on the function as .$timerId.
// That field is cleared when the timer is cancelled, but it is not returned
// from the queue until its time comes.
var timerIds = {};
var timerIdCounter = 1; // Counter used to assign ids.
// Zero-timer queue as simple array queue using push/shift.
var zeroTimerQueue = [];
function addTimer(f, ms) {
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);
f.$timerId = id;
timerIds[id] = f;
if (ms == 0 && !isNextTimerDue()) {
zeroTimerQueue.push(f);
} else {
addDelayedTimer(f, ms);
}
return id;
}
function nextZeroTimer() {
while (zeroTimerQueue.length > 0) {
var action = zeroTimerQueue.shift();
if (action.$timerId !== undefined) return action;
}
}
function nextEvent() {
var action = removeTask();
if (action) {
return action;
}
do {
action = nextZeroTimer();
if (action) break;
var nextList = nextDelayedTimerQueue();
if (!nextList) {
return;
}
var newTime = nextList.shift();
advanceTimeTo(newTime);
zeroTimerQueue = nextList;
} while (true)
var id = action.$timerId;
clearTimerId(action, id);
return action;
}
// Mocking time.
var timeOffset = 0;
var now = function() {
// Install the mock Date object only once.
// Following calls to "now" will just use the new (mocked) Date.now
// method directly.
installMockDate();
now = Date.now;
return Date.now();
};
var originalDate = Date;
var originalNow = originalDate.now;
function advanceTimeTo(time) {
var now = originalNow();
if (timeOffset < time - now) {
timeOffset = time - now;
}
}
function installMockDate() {
var NewDate = function Date(Y, M, D, h, m, s, ms) {
if (this instanceof Date) {
// Assume a construct call.
switch (arguments.length) {
case 0: return new originalDate(originalNow() + timeOffset);
case 1: return new originalDate(Y);
case 2: return new originalDate(Y, M);
case 3: return new originalDate(Y, M, D);
case 4: return new originalDate(Y, M, D, h);
case 5: return new originalDate(Y, M, D, h, m);
case 6: return new originalDate(Y, M, D, h, m, s);
default: return new originalDate(Y, M, D, h, m, s, ms);
}
}
return new originalDate(originalNow() + timeOffset).toString();
};
NewDate.UTC = originalDate.UTC;
NewDate.parse = originalDate.parse;
NewDate.now = function now() { return originalNow() + timeOffset; };
NewDate.prototype = originalDate.prototype;
originalDate.prototype.constructor = NewDate;
Date = NewDate;
}
// Heap priority queue with key index.
// Each entry is list of [timeout, callback1 ... callbackn].
var timerHeap = [];
var timerIndex = {};
function addDelayedTimer(f, ms) {
var timeout = now() + ms;
var timerList = timerIndex[timeout];
if (timerList == null) {
timerList = [timeout, f];
timerIndex[timeout] = timerList;
var index = timerHeap.length;
timerHeap.length += 1;
bubbleUp(index, timeout, timerList);
} else {
timerList.push(f);
}
}
function isNextTimerDue() {
if (timerHeap.length == 0) return false;
var head = timerHeap[0];
return head[0] < originalNow() + timeOffset;
}
function nextDelayedTimerQueue() {
if (timerHeap.length == 0) return null;
var result = timerHeap[0];
var last = timerHeap.pop();
if (timerHeap.length > 0) {
bubbleDown(0, last[0], last);
}
return result;
}
function bubbleUp(index, key, value) {
while (index != 0) {
var parentIndex = (index - 1) >> 1;
var parent = timerHeap[parentIndex];
var parentKey = parent[0];
if (key > parentKey) break;
timerHeap[index] = parent;
index = parentIndex;
}
timerHeap[index] = value;
}
function bubbleDown(index, key, value) {
while (true) {
var leftChildIndex = index * 2 + 1;
if (leftChildIndex >= timerHeap.length) break;
var minChildIndex = leftChildIndex;
var minChild = timerHeap[leftChildIndex];
var minChildKey = minChild[0];
var rightChildIndex = leftChildIndex + 1;
if (rightChildIndex < timerHeap.length) {
var rightChild = timerHeap[rightChildIndex];
var rightKey = rightChild[0];
if (rightKey < minChildKey) {
minChildIndex = rightChildIndex;
minChild = rightChild;
minChildKey = rightKey;
}
}
if (minChildKey > key) break;
timerHeap[index] = minChild;
index = minChildIndex;
}
timerHeap[index] = value;
}
function addInterval(f, ms) {
ms = Math.max(0, ms);
var id = timerIdCounter++;
function repeat() {
// Reactivate with the same id.
repeat.$timerId = id;
timerIds[id] = repeat;
addDelayedTimer(repeat, ms);
f();
}
repeat.$timerId = id;
timerIds[id] = repeat;
addDelayedTimer(repeat, ms);
return id;
}
function cancelTimer(id) {
var f = timerIds[id];
if (f == null) return;
clearTimerId(f, id);
}
function clearTimerId(f, id) {
f.$timerId = undefined;
delete timerIds[id];
}
async function eventLoop(action) {
while (action) {
try {
await action();
} catch (e) {
// JSC doesn't report/print uncaught async exceptions for some reason.
if (isJSC) {
print('Error: ' + e);
print('Stack: ' + e.stack);
}
if (typeof onerror == "function") {
onerror(e, null, -1);
} else {
throw e;
}
}
action = nextEvent();
}
}
// Global properties. "self" refers to the global object, so adding a
// property to "self" defines a global variable.
self.self = self;
self.dartMainRunner = function(main, ignored_args) {
// Initialize.
var action = async function() { await main(scriptArguments, null); }
eventLoop(action);
};
self.setTimeout = addTimer;
self.clearTimeout = cancelTimer;
self.setInterval = addInterval;
self.clearInterval = cancelTimer;
self.queueMicrotask = addTask;
self.readFileContentsAsBytes = readFileContentsAsBytes;
self.location = {}
self.location.href = 'file://' + args[wasmArg];
// Signals `Stopwatch._initTicker` to use `Date.now` to get ticks instead of
// `performance.now`, as it's not available in d8.
self.dartUseDateNowForTicks = true;
})(this, []);
// We would like this itself to be a ES module rather than a script, but
// unfortunately d8 does not return a failed error code if an unhandled
// exception occurs asynchronously in an ES module.
const main = async () => {
const dart2wasm = await import(args[jsRuntimeArg]);
/// Returns whether the `js-string` built-in is supported.
function detectImportedStrings() {
let bytes = [
0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0,
0, 2, 23, 1, 14, 119, 97, 115, 109, 58, 106, 115, 45,
115, 116, 114, 105, 110, 103, 4, 99, 97, 115, 116, 0, 0
];
return !WebAssembly.validate(
new Uint8Array(bytes), {builtins: ['js-string']});
}
function compile(filename, withJsStringBuiltins) {
// Create a Wasm module from the binary Wasm file.
return WebAssembly.compile(
readFileContentsAsBytes(filename),
withJsStringBuiltins ? {builtins: ['js-string']} : {}
);
}
globalThis.window ??= globalThis;
let importObject = {};
// Is an FFI module specified?
if (args.length > 2) {
// Instantiate FFI module.
var ffiInstance = await WebAssembly.instantiate(await compile(args[ffiArg], false), {});
// Make its exports available as imports under the 'ffi' module name.
importObject.ffi = ffiInstance.exports;
}
// Instantiate the Dart module, importing from the global scope.
var dartInstance = await dart2wasm.instantiate(
compile(args[wasmArg], detectImportedStrings()),
Promise.resolve(importObject),
);
// Call `main`. If tasks are placed into the event loop (by scheduling tasks
// explicitly or awaiting Futures), these will automatically keep the script
// alive even after `main` returns.
await dart2wasm.invoke(dartInstance, ...dartArgs);
};
dartMainRunner(main, []);

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,73 @@ 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 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 +404,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 +447,7 @@ a.button {
}
.benchmark-done h3 a:hover {
color: #34AADC;
color: var(--color-primary);
text-decoration: underline;
}
@ -371,7 +459,7 @@ a.button {
}
.benchmark-done h4 {
color: #34AADC;
color: var(--color-primary);
background-color: none;
}
@ -388,8 +476,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 +492,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 +520,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 +532,7 @@ a.button {
0% {
background-position: -1200px center;
}
100% {
background-position: 100vw center;
}
@ -433,6 +543,7 @@ a.button {
opacity: 0;
transform: rotateY(-85deg) translateZ(200px);
}
to {
opacity: 1;
transform: rotateY(0deg) translateZ(0px);
@ -451,14 +562,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,14 +24,6 @@
*/
"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)
{
@ -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>

20
package-lock.json generated
View file

@ -21,7 +21,7 @@
"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",
@ -5108,9 +5108,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 +5122,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": {
@ -6116,10 +6117,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

@ -23,11 +23,13 @@
"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: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",
@ -39,7 +41,6 @@
"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"
}
}

57
shell-config.js Normal file
View file

@ -0,0 +1,57 @@
/*
* Copyright (C) 2018 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.
*/
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

@ -53,7 +53,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;
}

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);

View file

@ -3,7 +3,8 @@
import serve from "./server.mjs";
import { Builder, Capabilities } from "selenium-webdriver";
import commandLineArgs from "command-line-args";
import commandLineUsage from "command-line-usage";
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." },
@ -11,35 +12,15 @@ const optionDefinitions = [
{ 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 BROWSER = options?.browser;
if (!BROWSER)
printHelp("No browser specified, use $BROWSER or --browser");
printHelp("No browser specified, use $BROWSER or --browser", optionDefinitions);
let capabilities;
switch (BROWSER) {
@ -65,24 +46,47 @@ switch (BROWSER) {
}
process.on("unhandledRejection", (err) => {
console.error(err);
logError(err);
process.exit(1);
});
process.once("uncaughtException", (err) => {
console.error(err);
logError(err);
process.exit(1);
});
const PORT = options.port;
const server = await serve(PORT);
async function testEnd2End() {
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 urlParams = Object.assign({
worstCaseCount: 2,
iterationCount: 3
}, params);
let results;
try {
const url = `http://localhost:${PORT}/index.html?worstCaseCount=2&iterationCount=3`;
console.log(`JetStream PREPARE ${url}`);
await driver.get(url);
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.
@ -90,24 +94,20 @@ async function testEnd2End() {
// 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()
callback();
});
results = await benchmarkResults(driver);
// FIXME: validate results;
console.log("\n✅ Tests completed!");
} catch(e) {
console.error("\n❌ Tests failed!");
console.error(e);
throw e;
} finally {
driver.quit();
server.close();
}
}
async function benchmarkResults(driver) {
console.log("JetStream START");
await driver.manage().setTimeouts({ script: 60_000 });
logInfo("JetStream START");
await driver.manage().setTimeouts({ script: 2 * 60_000 });
await driver.executeAsyncScript((callback) => {
globalThis.JetStream.start();
callback();
@ -122,7 +122,7 @@ async function benchmarkResults(driver) {
class JetStreamTestError extends Error {
constructor(errors) {
super(`Tests failed: ${errors.map(e => e.name).join(", ")}`);
super(`Tests failed: ${errors.map(e => e.stack).join(", ")}`);
this.errors = errors;
}
@ -160,4 +160,4 @@ function logIncrementalResult(previousResults, benchmarkResults) {
}
}
setImmediate(testEnd2End);
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

View file

@ -34,8 +34,10 @@ testList = [
"tfjs-wasm-simd",
"argon2-wasm",
"8bitbench-wasm",
"Dart-flute-wasm",
"Dart-flute-complex-wasm",
"Dart-flute-todomvc-wasm",
"zlib-wasm",
"Kotlin-compose-wasm",
];
// Reuse the full CLI runner, just with the subset of Wasm line items above.

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.

Binary file not shown.

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