Add branch for JetStream v2.1
This is a copy of JetStream v2.1 as of https://commits.webkit.org/254294@main
diff --git a/JetStream.css b/JetStream.css
index 1bd25bd..dcf5621 100644
--- a/JetStream.css
+++ b/JetStream.css
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 Apple Inc. All rights reserved.
+ * Copyright (C) 2018-2022 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -222,10 +222,8 @@
font-weight: 500;
letter-spacing: -0.10rem;
color: transparent;
- background-image: linear-gradient(132deg, #FFFFFF 0%, #FFFFFF 2%, #86D9FF 42%, #8BDAFF 84%, #FFFFFF 98%, #FFFFFF 100%);
+ background-image: linear-gradient(132deg, #96E5FF 0%, #96E5FF 2%, #86D9FF 42%, #8BDAFF 84%, #96E5FF 98%, #96E5FF 100%);
-webkit-background-clip: text;
- animation: shine 2s ease-in-out infinite;
- animation-delay: 2s;
background-size: 1200px 100%;
background-repeat: no-repeat;
-webkit-touch-callout: none;
diff --git a/JetStreamDriver.js b/JetStreamDriver.js
index c228f0c..e1a6683 100644
--- a/JetStreamDriver.js
+++ b/JetStreamDriver.js
@@ -1,7 +1,7 @@
"use strict";
/*
- * Copyright (C) 2018 Apple Inc. All rights reserved.
+ * Copyright (C) 2018-2022 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -25,7 +25,6 @@
* THE POSSIBILITY OF SUCH DAMAGE.
*/
-const preloadResources = !isInBrowser;
const measureTotalTimeAsSubtest = false; // Once we move to preloading all resources, it would be good to turn this on.
if (typeof RAMification === "undefined")
@@ -34,6 +33,15 @@
if (typeof testIterationCount === "undefined")
var testIterationCount = undefined;
+if (typeof testIterationCountMap === "undefined")
+ var testIterationCountMap = new Map;
+
+if (typeof testWorstCaseCountMap === "undefined")
+ var testWorstCaseCountMap = new Map;
+
+if (typeof dumpJSONResults === "undefined")
+ var dumpJSONResults = false;
+
// Used for the promise representing the current benchmark run.
this.currentResolve = null;
this.currentReject = null;
@@ -41,6 +49,49 @@
const defaultIterationCount = 120;
const defaultWorstCaseCount = 4;
+let showScoreDetails = false;
+let categoryScores = null;
+
+function displayCategoryScores() {
+ if (!categoryScores)
+ return;
+
+ let summaryElement = document.getElementById("result-summary");
+ for (let [category, scores] of categoryScores)
+ summaryElement.innerHTML += `<p> ${category}: ${uiFriendlyNumber(geomean(scores))}</p>`
+
+ categoryScores = null;
+}
+
+function getIterationCount(plan) {
+ if (testIterationCountMap.has(plan.name))
+ return testIterationCountMap.get(plan.name);
+ if (testIterationCount)
+ return testIterationCount;
+ if (plan.iterations)
+ return plan.iterations;
+ return defaultIterationCount;
+}
+
+function getWorstCaseCount(plan) {
+ if (testWorstCaseCountMap.has(plan.name))
+ return testWorstCaseCountMap.get(plan.name);
+ if (plan.worstCaseCount)
+ return plan.worstCaseCount;
+ return defaultWorstCaseCount;
+}
+
+if (isInBrowser) {
+ document.onkeydown = (keyboardEvent) => {
+ let key = keyboardEvent.key;
+ if (key === "d" || key === "D") {
+ showScoreDetails = true;
+
+ displayCategoryScores();
+ }
+ };
+}
+
function assert(b, m = "") {
if (!b)
throw new Error("Bad assertion: " + m);
@@ -124,11 +175,26 @@
if (!isInBrowser)
return Promise.resolve(readFile(url));
- let fetchResponse = await fetch(new Request(url));
+ let response;
+ let tries = 3;
+ while (tries--) {
+ let hasError = false;
+ try {
+ response = await fetch(url);
+ } catch (e) {
+ hasError = true;
+ }
+ if (!hasError && response.ok)
+ break;
+ if (tries)
+ continue;
+ window.allIsGood = false;
+ throw new Error("Fetch failed");
+ }
if (url.indexOf(".js") !== -1)
- return await fetchResponse.text();
+ return await response.text();
else if (url.indexOf(".wasm") !== -1)
- return await fetchResponse.arrayBuffer();
+ return await response.arrayBuffer();
throw new Error("should not be reached!");
}
@@ -148,6 +214,12 @@
class Driver {
constructor() {
this.benchmarks = [];
+ this.blobDataCache = { };
+ this.loadCache = { };
+ this.counter = { };
+ this.counter.loadedResources = 0;
+ this.counter.totalResources = 0;
+ this.counter.failedPreloadResources = 0;
}
addPlan(plan, BenchmarkClass = DefaultBenchmark) {
@@ -174,7 +246,6 @@
await updateUI();
try {
-
await benchmark.run();
} catch(e) {
JetStream.reportError(benchmark);
@@ -182,6 +253,16 @@
}
benchmark.updateUIAfterRun();
+
+ if (isInBrowser) {
+ let cache = JetStream.blobDataCache;
+ for (let file of benchmark.plan.files) {
+ let blobData = cache[file];
+ blobData.refCount--;
+ if (!blobData.refCount)
+ cache[file] = undefined;
+ }
+ }
}
let totalTime = Date.now() - start;
@@ -197,21 +278,54 @@
for (let benchmark of this.benchmarks)
allScores.push(benchmark.score);
+ categoryScores = new Map;
+ for (let benchmark of this.benchmarks) {
+ for (let category of Object.keys(benchmark.subTimes()))
+ categoryScores.set(category, []);
+ }
+
+ for (let benchmark of this.benchmarks) {
+ for (let [category, value] of Object.entries(benchmark.subTimes())) {
+ let arr = categoryScores.get(category);
+ arr.push(value);
+ }
+ }
+
if (isInBrowser) {
summaryElement.classList.add('done');
summaryElement.innerHTML = "<div class=\"score\">" + uiFriendlyNumber(geomean(allScores)) + "</div><label>Score</label>";
+ summaryElement.onclick = displayCategoryScores;
+ if (showScoreDetails)
+ displayCategoryScores();
statusElement.innerHTML = '';
- } else
+ } else {
+ console.log("\n");
+ for (let [category, scores] of categoryScores)
+ console.log(`${category}: ${uiFriendlyNumber(geomean(scores))}`);
+
console.log("\nTotal Score: ", uiFriendlyNumber(geomean(allScores)), "\n");
+ }
this.reportScoreToRunBenchmarkRunner();
+ this.dumpJSONResultsIfNeeded();
}
runCode(string)
{
if (!isInBrowser) {
let scripts = string;
- let globalObject = runString("");
+ let globalObject;
+ let realm;
+ if (isD8) {
+ realm = Realm.createAllowCrossRealmAccess();
+ globalObject = Realm.global(realm);
+ globalObject.loadString = function(s) {
+ return Realm.eval(realm, s);
+ };
+ globalObject.readFile = read;
+ } else
+ globalObject = runString("");
+
globalObject.console = {log:globalObject.print}
globalObject.top = {
currentResolve,
@@ -219,7 +333,8 @@
};
for (let script of scripts)
globalObject.loadString(script);
- return globalObject;
+
+ return isD8 ? realm : globalObject;
}
var magic = document.getElementById("magic");
@@ -286,6 +401,7 @@
}
async initialize() {
+ await this.prefetchResourcesForBrowser();
await this.fetchResources();
this.prepareToRun();
if (isInBrowser && window.location.search == '?report=true') {
@@ -293,9 +409,40 @@
}
}
- async fetchResources() {
+ async prefetchResourcesForBrowser() {
+ if (!isInBrowser)
+ return;
+
+ var promises = [];
for (let benchmark of this.benchmarks)
- await benchmark.fetchResources();
+ promises.push(benchmark.prefetchResourcesForBrowser());
+
+ await Promise.all(promises);
+
+ let counter = JetStream.counter;
+ if (counter.failedPreloadResources || counter.loadedResources != counter.totalResources) {
+ for (let benchmark of this.benchmarks) {
+ let allFilesLoaded = await benchmark.retryPrefetchResourcesForBrowser(counter);
+ if (allFilesLoaded)
+ break;
+ }
+
+ if (counter.failedPreloadResources || counter.loadedResources != counter.totalResources) {
+ // If we've failed to prefetch resources even after a sequential 1 by 1 retry,
+ // then fail out early rather than letting subtests fail with a hang.
+ window.allIsGood = false;
+ throw new Error("Fetch failed");
+ }
+ }
+
+ JetStream.loadCache = { }; // Done preloading all the files.
+ }
+
+ async fetchResources() {
+ var promises = [];
+ for (let benchmark of this.benchmarks)
+ promises.push(benchmark.fetchResources());
+ await Promise.all(promises);
if (!isInBrowser)
return;
@@ -310,14 +457,8 @@
}
}
- async reportScoreToRunBenchmarkRunner()
+ resultsJSON()
{
- if (!isInBrowser)
- return;
-
- if (window.location.search !== '?report=true')
- return;
-
let results = {};
for (let benchmark of this.benchmarks) {
const subResults = {}
@@ -331,12 +472,32 @@
"Time": ["Geometric"],
},
"tests": subResults,
- };;
+ };
}
results = {"JetStream2.0": {"metrics" : {"Score" : ["Geometric"]}, "tests" : results}};
- const content = JSON.stringify(results);
+ return JSON.stringify(results);
+ }
+
+ dumpJSONResultsIfNeeded()
+ {
+ if (dumpJSONResults) {
+ console.log("\n");
+ console.log(this.resultsJSON());
+ console.log("\n");
+ }
+ }
+
+ async reportScoreToRunBenchmarkRunner()
+ {
+ if (!isInBrowser)
+ return;
+
+ if (window.location.search !== '?report=true')
+ return;
+
+ const content = this.resultsJSON();
await fetch("/report", {
method: "POST",
heeaders: {
@@ -353,7 +514,7 @@
constructor(plan)
{
this.plan = plan;
- this.iterations = testIterationCount || plan.iterations || defaultIterationCount;
+ this.iterations = getIterationCount(plan);
this.isAsync = !!plan.isAsync;
this.scripts = null;
@@ -446,14 +607,15 @@
if (prerunCode)
addScript(prerunCode);
- if (preloadResources) {
+ if (!isInBrowser) {
assert(this.scripts && this.scripts.length === this.plan.files.length);
for (let text of this.scripts)
addScript(text);
} else {
+ let cache = JetStream.blobDataCache;
for (let file of this.plan.files)
- addScriptWithURL(file);
+ addScriptWithURL(cache[file].blobURL);
}
let promise = new Promise((resolve, reject) => {
@@ -494,27 +656,163 @@
this.processResults(results);
if (isInBrowser)
magicFrame.contentDocument.close();
+ else if (isD8)
+ Realm.dispose(magicFrame);
+ }
+
+ async doLoadBlob(resource) {
+ let response;
+ let tries = 3;
+ while (tries--) {
+ let hasError = false;
+ try {
+ response = await fetch(resource, { cache: "no-store" });
+ } catch (e) {
+ hasError = true;
+ }
+ if (!hasError && response.ok)
+ break;
+ if (tries)
+ continue;
+ throw new Error("Fetch failed");
+ }
+ let blob = await response.blob();
+ var blobData = JetStream.blobDataCache[resource];
+ blobData.blob = blob;
+ blobData.blobURL = URL.createObjectURL(blob);
+ return blobData;
+ }
+
+ async loadBlob(type, prop, resource, incrementRefCount = true) {
+ var blobData = JetStream.blobDataCache[resource];
+ if (!blobData) {
+ blobData = {
+ type: type,
+ prop: prop,
+ resource: resource,
+ blob: null,
+ blobURL: null,
+ refCount: 0
+ };
+ JetStream.blobDataCache[resource] = blobData;
+ }
+
+ if (incrementRefCount)
+ blobData.refCount++;
+
+ let promise = JetStream.loadCache[resource];
+ if (promise)
+ return await promise;
+
+ promise = this.doLoadBlob(resource);
+ JetStream.loadCache[resource] = promise;
+ return await promise;
+ }
+
+ updateCounter() {
+ let counter = JetStream.counter;
+ ++counter.loadedResources;
+ var statusElement = document.getElementById("status");
+ statusElement.innerHTML = `Loading ${counter.loadedResources} of ${counter.totalResources} ...`;
+ }
+
+ prefetchResourcesForBrowser() {
+ if (!isInBrowser)
+ return;
+ let promises = this.plan.files.map((file) => this.loadBlob("file", null, file).then((blobData) => {
+ if (!window.allIsGood)
+ return;
+ this.updateCounter();
+ }).catch((error) => {
+ // We'll try again later in retryPrefetchResourceForBrowser(). Don't throw an error.
+ }));
+
+ if (this.plan.preload) {
+ this.preloads = [];
+ for (let prop of Object.getOwnPropertyNames(this.plan.preload)) {
+ promises.push(this.loadBlob("preload", prop, this.plan.preload[prop]).then((blobData) => {
+ if (!window.allIsGood)
+ return;
+ this.preloads.push([ blobData.prop, blobData.blobURL ]);
+ this.updateCounter();
+ }).catch((error) => {
+ // We'll try again later in retryPrefetchResourceForBrowser(). Don't throw an error.
+ if (!this.failedPreloads)
+ this.failedPreloads = { };
+ this.failedPreloads[prop] = true;
+ JetStream.counter.failedPreloadResources++;
+ }));
+ }
+ }
+
+ JetStream.counter.totalResources += promises.length;
+ return Promise.all(promises);
+ }
+
+ async retryPrefetchResource(type, prop, file) {
+ let counter = JetStream.counter;
+ var blobData = JetStream.blobDataCache[file];
+ if (blobData.blob) {
+ // The same preload blob may be used by multiple subtests. Though the blob is already loaded,
+ // we still need to check if this subtest failed to load it before. If so, handle accordingly.
+ if (type == "preload") {
+ if (this.failedPreloads && this.failedPreloads[blobData.prop]) {
+ this.failedPreloads[blobData.prop] = false;
+ this.preloads.push([ blobData.prop, blobData.blobURL ]);
+ counter.failedPreloadResources--;
+ }
+ }
+ return !counter.failedPreloadResources && counter.loadedResources == counter.totalResources;
+ }
+
+ // Retry fetching the resource.
+ JetStream.loadCache[file] = null;
+ await this.loadBlob(type, prop, file, false).then((blobData) => {
+ if (!window.allIsGood)
+ return;
+ if (blobData.type == "preload")
+ this.preloads.push([ blobData.prop, blobData.blobURL ]);
+ this.updateCounter();
+ });
+
+ if (!blobData.blob) {
+ window.allIsGood = false;
+ throw new Error("Fetch failed");
+ }
+
+ return !counter.failedPreloadResources && counter.loadedResources == counter.totalResources;
+ }
+
+ async retryPrefetchResourcesForBrowser() {
+ if (!isInBrowser)
+ return;
+
+ let counter = JetStream.counter;
+ for (let resource of this.plan.files) {
+ let allDone = await this.retryPrefetchResource("file", null, resource);
+ if (allDone)
+ return true; // All resources loaded, nothing more to do.
+ }
+
+ if (this.plan.preload) {
+ for (let prop of Object.getOwnPropertyNames(this.plan.preload)) {
+ let resource = this.plan.preload[prop];
+ let allDone = await this.retryPrefetchResource("preload", prop, resource);
+ if (allDone)
+ return true; // All resources loaded, nothing more to do.
+ }
+ }
+ return !counter.failedPreloadResources && counter.loadedResources == counter.totalResources;
}
fetchResources() {
if (this._resourcesPromise)
return this._resourcesPromise;
- let filePromises = preloadResources ? this.plan.files.map((file) => fileLoader.load(file)) : [];
- let preloads = [];
- let preloadVariableNames = [];
+ let filePromises = !isInBrowser ? this.plan.files.map((file) => fileLoader.load(file)) : [];
- if (isInBrowser && this.plan.preload) {
- for (let prop of Object.getOwnPropertyNames(this.plan.preload)) {
- preloadVariableNames.push(prop);
- preloads.push(this.plan.preload[prop]);
- }
- }
-
- preloads = preloads.map((file) => fileLoader.load(file));
-
- let p1 = Promise.all(filePromises).then((texts) => {
- if (!preloadResources)
+ let promise = Promise.all(filePromises).then((texts) => {
+ if (isInBrowser)
return;
this.scripts = [];
assert(texts.length === this.plan.files.length);
@@ -522,26 +820,10 @@
this.scripts.push(text);
});
- let p2 = Promise.all(preloads).then((data) => {
- this.preloads = [];
- this.blobs = [];
- for (let i = 0; i < data.length; ++i) {
- let item = data[i];
+ this.preloads = [];
+ this.blobs = [];
- let blob;
- if (typeof item === "string") {
- blob = new Blob([item], {type : 'application/javascript'});
- } else if (item instanceof ArrayBuffer) {
- blob = new Blob([item], {type : 'application/octet-stream'});
- } else
- throw new Error("Unexpected item!");
-
- this.blobs.push(blob);
- this.preloads.push([preloadVariableNames[i], URL.createObjectURL(blob)]);
- }
- });
-
- this._resourcesPromise = Promise.all([p1, p2]);
+ this._resourcesPromise = promise;
return this._resourcesPromise;
}
@@ -578,7 +860,7 @@
constructor(...args) {
super(...args);
- this.worstCaseCount = this.plan.worstCaseCount || defaultWorstCaseCount;
+ this.worstCaseCount = getWorstCaseCount(this.plan);
this.firstIteration = null;
this.worst4 = null;
this.average = null;
@@ -638,15 +920,15 @@
return;
}
- print(" Startup:", uiFriendlyNumber(this.firstIteration));
- print(" Worst Case:", uiFriendlyNumber(this.worst4));
- print(" Average:", uiFriendlyNumber(this.average));
- print(" Score:", uiFriendlyNumber(this.score));
+ console.log(" Startup:", uiFriendlyNumber(this.firstIteration));
+ console.log(" Worst Case:", uiFriendlyNumber(this.worst4));
+ console.log(" Average:", uiFriendlyNumber(this.average));
+ console.log(" Score:", uiFriendlyNumber(this.score));
if (RAMification) {
- print(" Current Footprint:", uiFriendlyNumber(this.currentFootprint));
- print(" Peak Footprint:", uiFriendlyNumber(this.peakFootprint));
+ console.log(" Current Footprint:", uiFriendlyNumber(this.currentFootprint));
+ console.log(" Peak Footprint:", uiFriendlyNumber(this.peakFootprint));
}
- print(" Wall time:", uiFriendlyDuration(new Date(this.endTime - this.startTime)));
+ console.log(" Wall time:", uiFriendlyDuration(new Date(this.endTime - this.startTime)));
}
}
@@ -732,14 +1014,14 @@
return;
}
- print(" Stdlib:", uiFriendlyNumber(this.stdlib));
- print(" Tests:", uiFriendlyNumber(this.mainRun));
- print(" Score:", uiFriendlyNumber(this.score));
+ console.log(" Stdlib:", uiFriendlyNumber(this.stdlib));
+ console.log(" Tests:", uiFriendlyNumber(this.mainRun));
+ console.log(" Score:", uiFriendlyNumber(this.score));
if (RAMification) {
- print(" Current Footprint:", uiFriendlyNumber(this.currentFootprint));
- print(" Peak Footprint:", uiFriendlyNumber(this.peakFootprint));
+ console.log(" Current Footprint:", uiFriendlyNumber(this.currentFootprint));
+ console.log(" Peak Footprint:", uiFriendlyNumber(this.peakFootprint));
}
- print(" Wall time:", uiFriendlyDuration(new Date(this.endTime - this.startTime)));
+ console.log(" Wall time:", uiFriendlyDuration(new Date(this.endTime - this.startTime)));
}
};
@@ -888,13 +1170,13 @@
document.getElementById(this.scoreID).innerHTML = uiFriendlyNumber(this.score);
return;
}
- print(" Startup:", uiFriendlyNumber(this.startupTime));
- print(" Run time:", uiFriendlyNumber(this.runTime));
+ console.log(" Startup:", uiFriendlyNumber(this.startupTime));
+ console.log(" Run time:", uiFriendlyNumber(this.runTime));
if (RAMification) {
- print(" Current Footprint:", uiFriendlyNumber(this.currentFootprint));
- print(" Peak Footprint:", uiFriendlyNumber(this.peakFootprint));
+ console.log(" Current Footprint:", uiFriendlyNumber(this.currentFootprint));
+ console.log(" Peak Footprint:", uiFriendlyNumber(this.peakFootprint));
}
- print(" Score:", uiFriendlyNumber(this.score));
+ console.log(" Score:", uiFriendlyNumber(this.score));
}
};
@@ -1542,6 +1824,8 @@
let runSeaMonster = true;
let runCodeLoad = true;
let runWasm = true;
+if (typeof WebAssembly === "undefined")
+ runWasm = false;
if (false) {
runOctane = false;
diff --git a/LuaJSFight/hello_world.js b/LuaJSFight/hello_world.js
index 35ecf31..defc677 100644
--- a/LuaJSFight/hello_world.js
+++ b/LuaJSFight/hello_world.js
@@ -1,5 +1 @@
-class Benchmark {
- runIteration() {
- print("Hello world!");
- }
-}
+print("Hello world!");
diff --git a/LuaJSFight/list_search.js b/LuaJSFight/list_search.js
index 1775fe2..878a14b 100644
--- a/LuaJSFight/list_search.js
+++ b/LuaJSFight/list_search.js
@@ -8,16 +8,5 @@
}
nums = [1, 2, 3, 4, 5, 6, 7];
function isEven(x) { return (x & 1) == 0; }
-
-function run()
-{
- firstEven = firstWhere(nums, isEven);
- print('First even: ' + firstEven)
-}
-
-class Benchmark {
- runIteration() {
- run();
- }
-}
-
+firstEven = firstWhere(nums, isEven);
+print('First even: ' + firstEven)
diff --git a/LuaJSFight/lists.js b/LuaJSFight/lists.js
index 8300ae4..c6bf148 100644
--- a/LuaJSFight/lists.js
+++ b/LuaJSFight/lists.js
@@ -2,17 +2,7 @@
var i = 0;
var items = [];
-function run()
-{
- items = [];
- while (i < n) {
- items.push(i);
- i = i + 1;
- }
-}
-
-class Benchmark {
- runIteration() {
- run();
- }
+while (i < n) {
+ items.push(i);
+ i = i + 1;
}
diff --git a/LuaJSFight/richards.js b/LuaJSFight/richards.js
new file mode 100644
index 0000000..330c394
--- /dev/null
+++ b/LuaJSFight/richards.js
@@ -0,0 +1,537 @@
+// 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();
diff --git a/LuaJSFight/string_lists.js b/LuaJSFight/string_lists.js
index 40ab017..774dcfb 100644
--- a/LuaJSFight/string_lists.js
+++ b/LuaJSFight/string_lists.js
@@ -2,17 +2,7 @@
var i = 0;
var items = [];
-function run()
-{
- items = [];
- while (i < n) {
- items.push("digit" + i);
- i = i + 1;
- }
-}
-
-class Benchmark {
- runIteration() {
- run();
- }
+while (i < n) {
+ items.push("digit" + i);
+ i = i + 1;
}
diff --git a/RAMification.py b/RAMification.py
new file mode 100644
index 0000000..51edf1c
--- /dev/null
+++ b/RAMification.py
@@ -0,0 +1,376 @@
+# Copyright (C) 2018-2019 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.
+
+import argparse
+import collections
+import json
+import math
+import os
+import re
+import subprocess
+import sys
+
+jitTests = ["3d-cube-SP", "3d-raytrace-SP", "acorn-wtb", "ai-astar", "Air", "async-fs", "Babylon", "babylon-wtb", "base64-SP", "Basic", "Box2D", "cdjs", "chai-wtb", "coffeescript-wtb", "crypto", "crypto-aes-SP", "crypto-md5-SP", "crypto-sha1-SP", "date-format-tofte-SP", "date-format-xparb-SP", "delta-blue", "earley-boyer", "espree-wtb", "first-inspector-code-load", "FlightPlanner", "float-mm.c", "gaussian-blur", "gbemu", "gcc-loops-wasm", "hash-map", "HashSet-wasm", "jshint-wtb", "json-parse-inspector", "json-stringify-inspector", "lebab-wtb", "mandreel", "ML", "multi-inspector-code-load", "n-body-SP", "navier-stokes", "octane-code-load", "octane-zlib", "OfflineAssembler", "pdfjs", "prepack-wtb", "quicksort-wasm", "raytrace", "regex-dna-SP", "regexp", "richards", "richards-wasm", "splay", "stanford-crypto-aes", "stanford-crypto-pbkdf2", "stanford-crypto-sha256", "string-unpack-code-SP", "tagcloud-SP", "tsf-wasm", "typescript", "uglify-js-wtb", "UniPoker", "WSL"]
+
+nonJITTests = ["3d-cube-SP", "3d-raytrace-SP", "acorn-wtb", "ai-astar", "Air", "async-fs", "Babylon", "babylon-wtb", "base64-SP", "Basic", "Box2D", "cdjs", "chai-wtb", "coffeescript-wtb", "crypto-aes-SP", "delta-blue", "earley-boyer", "espree-wtb", "first-inspector-code-load", "gaussian-blur", "gbemu", "hash-map", "jshint-wtb", "json-parse-inspector", "json-stringify-inspector", "lebab-wtb", "mandreel", "ML", "multi-inspector-code-load", "octane-code-load", "OfflineAssembler", "pdfjs", "prepack-wtb", "raytrace", "regex-dna-SP", "regexp", "splay", "stanford-crypto-aes", "string-unpack-code-SP", "tagcloud-SP", "typescript", "uglify-js-wtb"]
+
+# Run two groups of tests with each group in a single JSC instance to see how well memory recovers between tests.
+groupTests = ["typescript,acorn-wtb,Air,pdfjs,crypto-aes-SP", "splay,FlightPlanner,prepack-wtb,octane-zlib,3d-cube-SP"]
+
+luaTests = [("hello_world-LJF", "LuaJSFight/hello_world.js", 5), ("list_search-LJF", "LuaJSFight/list_search.js", 5), ("lists-LJF", "LuaJSFight/lists.js", 5), ("string_lists-LJF", "LuaJSFight/string_lists.js", 5), ("richards", "LuaJSFight/richards.js", 5)]
+
+oneMB = float(1024 * 1024)
+footprintRE = re.compile(r"Current Footprint: (\d+(?:.\d+)?)")
+peakFootprintRE = re.compile(r"Peak Footprint: (\d+(?:.\d+)?)")
+
+TestResult = collections.namedtuple("TestResult", ["name", "returnCode", "footprint", "peakFootprint", "vmmapOutput", "smapsOutput"])
+
+ramification_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+
+remoteHooks = {}
+
+
+def mean(values):
+ if not len(values):
+ return None
+
+ return sum(values) / len(values)
+
+
+def geomean(values):
+ if not len(values):
+ return None
+
+ product = 1.0
+
+ for x in values:
+ product *= x
+
+ return math.pow(product, (1.0 / len(values)))
+
+
+def frameworkPathFromExecutablePath(execPath):
+ if not os.path.abspath(execPath):
+ execPath = os.path.isabs(execPath)
+
+ pathMatch = re.match("(.*?/WebKitBuild/(Release|Debug)+)/([a-zA-Z]+)$", execPath)
+ if pathMatch:
+ return pathMatch.group(1)
+
+ pathMatch = re.match("(.*?)/JavaScriptCore.framework/Resources/([a-zA-Z]+)$", execPath)
+ if pathMatch:
+ return pathMatch.group(1)
+
+ pathMatch = re.match("(.*?)/JavaScriptCore.framework/Helpers/([a-zA-Z]+)$", execPath)
+ if pathMatch:
+ return pathMatch.group(1)
+
+ pathMatch = re.match("(.*?/(Release|Debug)+)/([a-zA-Z]+)$", execPath)
+ if pathMatch:
+ return pathMatch.group(1)
+
+ pathMatch = re.match("(.*?)/JavaScriptCore.framework/(.*?)/Resources/([a-zA-Z]+)$", execPath)
+ if pathMatch:
+ return pathMatch.group(1)
+
+
+def parseArgs(parser=None):
+ def optStrToBool(arg):
+ if arg.lower() in ("true", "t", "yes", "y"):
+ return True
+ if arg.lower() in ("false", "f", "no", "n"):
+ return False
+
+ raise argparse.ArgumentTypeError("Boolean value expected")
+
+ if not parser:
+ parser = argparse.ArgumentParser(description="RAMification benchmark driver script")
+ parser.set_defaults(runner=LocalRunner)
+
+ verbosityGroup = parser.add_mutually_exclusive_group()
+ verbosityGroup.add_argument("-q", "--quiet", dest="verbose", action="store_false", help="Provide less output")
+ verbosityGroup.add_argument("-v", "--verbose", dest="verbose", action="store_true", default=True, help="Provide more output")
+
+ parser.add_argument("-c", "--jsc", dest="jscCommand", type=str, default="/usr/local/bin/jsc", metavar="path-to-jsc", help="Path to jsc command")
+ parser.add_argument("-d", "--jetstream2-dir", dest="testDir", type=str, default=ramification_dir, metavar="path-to-JetStream2-files", help="JetStream2 root directory")
+ parser.add_argument("-e", "--env-var", dest="extraEnvVars", action="append", default=[], metavar="env-var=value", help="Specify additional environment variables")
+ parser.add_argument("-f", "--format-json", dest="formatJSON", action="store_true", default=False, help="Format JSON with whitespace")
+ parser.add_argument("-g", "--run-grouped-tests", dest="runGroupedTests", nargs="?", const=True, default=None, type=optStrToBool, metavar="true / false", help="Run grouped tests [default]")
+ parser.add_argument("-j", "--run-jit", dest="runJITTests", nargs="?", const=True, default=None, type=optStrToBool, metavar="true / false", help="Run JIT tests [default]")
+ parser.add_argument("-l", "--lua", dest="runLuaTests", nargs="?", const=True, default=None, type=optStrToBool, metavar="true / false", help="Run Lua comparison tests [default]")
+ parser.add_argument("-n", "--run-no-jit", dest="runNoJITTests", nargs="?", const=True, default=None, type=optStrToBool, metavar="true / false", help="Run no JIT tests [default]")
+ parser.add_argument("-o", "--output", dest="jsonFilename", type=str, default=None, metavar="JSON-output-file", help="Path to JSON output")
+ parser.add_argument("-m", "--vmmap", dest="takeVmmap", action="store_true", default=False, help="Take a vmmap after each test")
+ parser.add_argument("--smaps", dest="takeSmaps", action="store_true", default=False, help="Take a smaps rollup after each test")
+
+ args = parser.parse_args()
+
+ subtestArgs = [args.runGroupedTests, args.runJITTests, args.runLuaTests, args.runNoJITTests]
+ allDefault = all([arg is None for arg in subtestArgs])
+ anyTrue = any([arg is True for arg in subtestArgs])
+ anyFalse = any([arg is False for arg in subtestArgs])
+
+ # Default behavior is to run all subtests.
+ # If a test is explicitly specified not to run, skip that test and use the default behavior for the remaining tests.
+ # If tests are explicitly specified to run, only run those tests.
+ # If there is a mix of tests specified to run and not to run, also do not run any unspecified tests.
+ getArgValue = lambda arg: True if allDefault else False if arg is None and anyTrue else True if arg is None and anyFalse else arg
+
+ args.runJITTests = getArgValue(args.runJITTests)
+ args.runNoJITTests = getArgValue(args.runNoJITTests)
+ args.runLuaTests = getArgValue(args.runLuaTests)
+ args.runGroupedTests = getArgValue(args.runGroupedTests)
+
+ return args
+
+
+class BaseRunner:
+ def __init__(self, args):
+ self.rootDir = args.testDir
+ self.environmentVars = {}
+ self.vmmapOutput = "" if args.takeVmmap else None
+ self.smapsOutput = "" if args.takeSmaps else None
+
+ def setup(self):
+ pass
+
+ def setEnv(self, variable, value):
+ self.environmentVars[variable] = value
+
+ def unsetEnv(self, variable):
+ self.environmentVars.pop(variable, None)
+
+ def resetForTest(self, testName):
+ self.testName = testName
+ self.footprint = None
+ self.peakFootprint = None
+ self.returnCode = 0
+
+ def processLine(self, line):
+ line = str(line.strip())
+
+ footprintMatch = re.match(footprintRE, line)
+ if footprintMatch:
+ self.footprint = int(footprintMatch.group(1))
+ return
+
+ peakFootprintMatch = re.match(peakFootprintRE, line)
+ if peakFootprintMatch:
+ self.peakFootprint = int(peakFootprintMatch.group(1))
+
+ def setReturnCode(self, returnCode):
+ self.returnCode = returnCode
+
+ def getResults(self):
+ return TestResult(name=self.testName, returnCode=self.returnCode, footprint=self.footprint, peakFootprint=self.peakFootprint, vmmapOutput=self.vmmapOutput, smapsOutput=self.smapsOutput)
+
+
+class LocalRunner(BaseRunner):
+ def __init__(self, args):
+ BaseRunner.__init__(self, args)
+ self.jscCommand = args.jscCommand
+
+ def runOneTest(self, test, extraOptions=None, useJetStream2Harness=True):
+ self.resetForTest(test)
+
+ args = [self.jscCommand]
+ if extraOptions:
+ args.extend(extraOptions)
+
+ if useJetStream2Harness:
+ args.extend(["-e", "testList='{test}'; runMode='RAMification'".format(test=test), "cli.js"])
+ else:
+ args.extend(["--footprint", "{test}".format(test=test)])
+
+ self.resetForTest(test)
+
+ proc = subprocess.Popen(args, cwd=self.rootDir, env=self.environmentVars, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=None, shell=False)
+ while True:
+ line = proc.stdout.readline()
+ if sys.version_info[0] >= 3:
+ line = str(line, "utf-8")
+
+ self.processLine(line)
+
+ if "js shell waiting for input to exit" in line:
+ if self.vmmapOutput is not None:
+ self.vmmapOutput = subprocess.Popen(['vmmap', '--summary', '{}'.format(proc.pid)], shell=False, stderr=subprocess.PIPE, stdout=subprocess.PIPE).stdout.read()
+ if sys.version_info[0] >= 3:
+ self.vmmapOutput = str(self.vmmapOutput, "utf-8")
+ if self.smapsOutput is not None:
+ self.smapsOutput = subprocess.Popen(['cat', '/proc/{}/smaps_rollup'.format(proc.pid)], shell=False, stderr=subprocess.PIPE, stdout=subprocess.PIPE).stdout.read()
+ proc.stdin.write(b"done\n")
+ proc.stdin.flush()
+
+ if line == "":
+ break
+
+ self.setReturnCode(proc.wait())
+
+ return self.getResults()
+
+
+def main(parser=None):
+ footprintValues = []
+ peakFootprintValues = []
+ testResultsDict = {}
+ hasFailedRuns = False
+
+ args = parseArgs(parser=parser)
+
+ testRunner = args.runner(args)
+
+ if args.takeVmmap or args.takeSmaps:
+ testRunner.setEnv("JS_SHELL_WAIT_FOR_INPUT_TO_EXIT", "1")
+
+ dyldFrameworkPath = frameworkPathFromExecutablePath(args.jscCommand)
+ if dyldFrameworkPath:
+ testRunner.setEnv("DYLD_FRAMEWORK_PATH", dyldFrameworkPath)
+
+ for envVar in args.extraEnvVars:
+ envVarParts = envVar.split("=")
+ if len(envVarParts) == 1:
+ envVarParts[1] = "1"
+ testRunner.setEnv(envVarParts[0], envVarParts[1])
+
+ testRunner.setup()
+
+ def runTestList(testList, extraOptions=None, useJetStream2Harness=True):
+ testScoresDict = {}
+
+ for testInfo in testList:
+ footprintScores = []
+ peakFootprintScores = []
+ if isinstance(testInfo, tuple):
+ testName, test, weight = testInfo
+ else:
+ testName, test, weight = testInfo, testInfo, 1
+
+ sys.stdout.write("Running {}... ".format(testName))
+ testResult = testRunner.runOneTest(test, extraOptions, useJetStream2Harness)
+
+ if testResult.returnCode == 0 and testResult.footprint and testResult.peakFootprint:
+ if args.verbose:
+ print("footprint: {}, peak footprint: {}".format(testResult.footprint, testResult.peakFootprint))
+ if testResult.vmmapOutput:
+ print(testResult.vmmapOutput)
+ if testResult.smapsOutput:
+ print(testResult.smapsOutput)
+ else:
+ print
+
+ if testResult.footprint:
+ footprintScores.append(int(testResult.footprint))
+ for count in range(0, weight):
+ footprintValues.append(testResult.footprint / oneMB)
+ if testResult.peakFootprint:
+ peakFootprintScores.append(int(testResult.peakFootprint))
+ for count in range(0, weight):
+ peakFootprintValues.append(testResult.peakFootprint / oneMB)
+ else:
+ hasFailedRuns = True
+ print("failed")
+ if testResult.returnCode != 0:
+ print(" exit code = {}".format(testResult.returnCode))
+ if not testResult.footprint:
+ print(" footprint = {}".format(testResult.footprint))
+ if not testResult.peakFootprint:
+ print(" peak footprint = {}".format(testResult.peakFootprint))
+ print
+
+ testScoresDict[test] = {"metrics": {"Allocations": ["Geometric"]}, "tests": {"end": {"metrics": {"Allocations": {"current": footprintScores}}}, "peak": {"metrics": {"Allocations": {"current": peakFootprintScores}}}}}
+
+ return testScoresDict
+
+ current_path = os.getcwd()
+ os.chdir(ramification_dir) # To allow JS libraries to load
+
+ if args.runLuaTests:
+ if args.verbose:
+ print("== LuaJSFight No JIT tests ==")
+
+ # Use system malloc for LuaJSFight tests
+ testRunner.setEnv("Malloc", "X")
+
+ scoresDict = runTestList(luaTests, ["--useJIT=false", "--forceMiniVMMode=true"], useJetStream2Harness=False)
+
+ testResultsDict["LuaJSFight No JIT Tests"] = {"metrics": {"Allocations": ["Geometric"]}, "tests": scoresDict}
+
+ testRunner.unsetEnv("Malloc")
+
+ if args.runGroupedTests:
+ if args.verbose:
+ print("== Grouped tests ==")
+
+ scoresDict = runTestList(groupTests)
+
+ testResultsDict["Grouped Tests"] = {"metrics": {"Allocations": ["Geometric"]}, "tests": scoresDict}
+
+ if args.runJITTests:
+ if args.verbose:
+ print("== JIT tests ==")
+
+ scoresDict = runTestList(jitTests)
+
+ testResultsDict["JIT Tests"] = {"metrics": {"Allocations": ["Geometric"]}, "tests": scoresDict}
+
+ if args.runNoJITTests:
+ if args.verbose:
+ print("== No JIT tests ==")
+
+ scoresDict = runTestList(nonJITTests, ["--useJIT=false", "-e", "testIterationCount=1"])
+
+ testResultsDict["No JIT Tests"] = {"metrics": {"Allocations": ["Geometric"]}, "tests": scoresDict}
+
+ footprintGeomean = int(geomean(footprintValues) * oneMB)
+ peakFootprintGeomean = int(geomean(peakFootprintValues) * oneMB)
+ totalScore = int(geomean([footprintGeomean, peakFootprintGeomean]))
+
+ if footprintGeomean:
+ print("Footprint geomean: {} ({:.3f} MB)".format(footprintGeomean, footprintGeomean / oneMB))
+
+ if peakFootprintGeomean:
+ print("Peak Footprint geomean: {} ({:.3f} MB)".format(peakFootprintGeomean, peakFootprintGeomean / oneMB))
+
+ if footprintGeomean and peakFootprintGeomean:
+ print("Score: {} ({:.3f} MB)".format(totalScore, totalScore / oneMB))
+
+ resultsDict = {"RAMification": {"metrics": {"Allocations": {"current": [totalScore]}}, "tests": testResultsDict}}
+
+ os.chdir(current_path) # Reset the path back to what it was before
+
+ if args.jsonFilename:
+ with open(args.jsonFilename, "w") as jsonFile:
+ if args.formatJSON:
+ json.dump(resultsDict, jsonFile, indent=4, separators=(',', ': '))
+ else:
+ json.dump(resultsDict, jsonFile)
+
+ if hasFailedRuns:
+ print("Detected failed run(s), exiting with non-zero return code")
+
+ return hasFailedRuns
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/RexBench/UniPoker/benchmark.js b/RexBench/UniPoker/benchmark.js
index f87b06e..cbcbe0b 100644
--- a/RexBench/UniPoker/benchmark.js
+++ b/RexBench/UniPoker/benchmark.js
@@ -43,8 +43,9 @@
{
if (this._players.length != playerExpectations.length)
throw "Expect " + playerExpectations.length + ", but actually have " + this._players.length;
-
- for (let playerIdx = 0; playerIdx < playerExpectations.length; playerIdx++)
- playerExpectations[playerIdx].validate(this._players[playerIdx]);
+ if (isInBrowser) {
+ for (let playerIdx = 0; playerIdx < playerExpectations.length; playerIdx++)
+ playerExpectations[playerIdx].validate(this._players[playerIdx]);
+ }
}
}
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/__init__.py
diff --git a/cli.js b/cli.js
index e3b98db..df7676a 100644
--- a/cli.js
+++ b/cli.js
@@ -28,6 +28,10 @@
log: print
}
+const isD8 = typeof Realm !== "undefined";
+if (isD8)
+ readFile = read;
+
if (typeof testList === "undefined")
testList = undefined;
diff --git a/in-depth.html b/in-depth.html
index 5a6be6c..9def091 100644
--- a/in-depth.html
+++ b/in-depth.html
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2019 Apple Inc. All rights reserved.
+ Copyright (C) 2019-2022 Apple Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
@@ -27,7 +27,7 @@
<head>
<meta charset="utf-8" />
- <title>JetStream 2 In-Depth Analysis</title>
+ <title>JetStream 2.1 In-Depth Analysis</title>
<link rel="stylesheet" href="JetStream.css">
@@ -35,7 +35,7 @@
<body>
<h1 class="logo">
<div id="jetstreams">
- <a href="index.html" class="logo-image">JetStream 2</a>
+ <a href="index.html" class="logo-image">JetStream 2.1</a>
</div>
</h1>
<main>
@@ -87,7 +87,7 @@
</p>
<p>
- All but one of JetStream'2 JavaScript benchmarks run for N iterations, where
+ All but one of JetStream 2's JavaScript benchmarks run for N iterations, where
N is usually 120. JetStream 2 reports the startup score as the time it takes to run the first iteration.
The worst case score is the average of the worst M iterations, excluding the first iteration.
M is always less than N, and is usually 4. The average case score is the average
@@ -113,12 +113,19 @@
</p>
<p>
- Note that scores from JetStream 2 are not comparable to scores to other versions
+ JetStream 2.1 runs the same benchmarks as JetStream 2, but updates the benchmark driver to
+ improve score stability. This is achieved by pre-fetching network resources prior to running
+ the benchmarks. This can reduce perturbations on the measurement of JavaScript execution
+ time due to second order effects of pause times induced by network latency.
+ </p>
+
+ <p>
+ Note that scores from JetStream 2.1 are not comparable to scores to other versions
of any JetStream benchmark.
</p>
<h3>
- JetStream 2 has 64 subtests:
+ JetStream 2.1 has 64 subtests:
</h3>
<dl>
diff --git a/index.html b/index.html
index be216c4..54cfffa 100644
--- a/index.html
+++ b/index.html
@@ -1,5 +1,5 @@
<!--
- Copyright (C) 2019 Apple Inc. All rights reserved.
+ Copyright (C) 2019-2022 Apple Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
@@ -27,20 +27,34 @@
<head>
<meta charset="utf-8" />
- <title>JetStream 2</title>
+ <title>JetStream 2.1</title>
<link rel="stylesheet" href="JetStream.css">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes, viewport-fit=cover">
<script>
const isInBrowser = true;
+ const isD8 = false;
var allIsGood = true;
- window.onerror = function() { allIsGood = false; }
+ window.onerror = function(e) {
+ if (e == "Script error.") {
+ // This is a workaround for Firefox on iOS which has an uncaught exception from
+ // its injected script. We should not stop the benchmark from running due to that
+ // if it can otherwise run.
+ return;
+ }
+ allIsGood = false;
+ }
async function initialize() {
- if (allIsGood)
- await JetStream.initialize();
- else {
+ if (allIsGood) {
+ try {
+ await JetStream.initialize();
+ } catch (e) {
+ allIsGood = false;
+ }
+ }
+ if (!allIsGood) {
let statusElement = document.getElementById("status");
statusElement.classList.remove('loading');
statusElement.classList.add('error');
@@ -60,7 +74,7 @@
</h1>
<main>
- <p class="summary">JetStream 2 is a JavaScript and WebAssembly benchmark suite focused on the most advanced web applications. It rewards browsers that start up quickly, execute code quickly, and run smoothly. For more information, read the <a href="in-depth.html">in-depth analysis</a>. Bigger scores are better.</p>
+ <p class="summary">JetStream 2.1 is a JavaScript and WebAssembly benchmark suite focused on the most advanced web applications. It rewards browsers that start up quickly, execute code quickly, and run smoothly. For more information, read the <a href="in-depth.html">in-depth analysis</a>. Bigger scores are better.</p>
<p class="summary" id="mode-description"></p>
<div id="result-summary"></div>
diff --git a/wasm-cli.js b/wasm-cli.js
new file mode 100644
index 0000000..8020303
--- /dev/null
+++ b/wasm-cli.js
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+const isInBrowser = false;
+console = {
+ log: () => { }
+}
+
+testList = ["HashSet-wasm", "tsf-wasm", "quicksort-wasm", "gcc-loops-wasm", "richards-wasm"];
+
+RAMification = false;
+
+load("./JetStreamDriver.js");
+
+async function runJetStream() {
+ try {
+ await JetStream.initialize();
+ JetStream.start();
+ } catch (e) {
+ throw e;
+ }
+}
+runJetStream();
diff --git a/wasm/HashSet.cpp b/wasm/HashSet.cpp
index eca979b..a8cea65 100644
--- a/wasm/HashSet.cpp
+++ b/wasm/HashSet.cpp
@@ -42,7 +42,7 @@
} \
} while(0)
-#define ASSERT_DISABLED 1
+#define ASSERT_ENABLED 0
#define DUMP_HASHTABLE_STATS 0
#define DUMP_HASHTABLE_STATS_PER_TABLE 0
@@ -1033,7 +1033,7 @@
template<typename HashTranslator, typename T> ValueType* lookup(const T&);
template<typename HashTranslator, typename T> ValueType* inlineLookup(const T&);
-#if !ASSERT_DISABLED
+#if ASSERT_ENABLED
void checkTableConsistency() const;
#else
static void checkTableConsistency() { }
@@ -1085,7 +1085,7 @@
iterator makeKnownGoodIterator(ValueType* pos) { return iterator(this, pos, m_table + m_tableSize, HashItemKnownGood); }
const_iterator makeKnownGoodConstIterator(ValueType* pos) const { return const_iterator(this, pos, m_table + m_tableSize, HashItemKnownGood); }
-#if !ASSERT_DISABLED
+#if ASSERT_ENABLED
void checkTableConsistencyExceptSize() const;
#else
static void checkTableConsistencyExceptSize() { }
@@ -1182,7 +1182,7 @@
return key;
}
-#if ASSERT_DISABLED
+#if !ASSERT_ENABLED
template<typename Key, typename Value, typename Extractor, typename HashFunctions, typename Traits, typename KeyTraits>
template<typename HashTranslator, typename T>
@@ -1951,7 +1951,7 @@
return *this;
}
-#if !ASSERT_DISABLED
+#if ASSERT_ENABLED
template<typename Key, typename Value, typename Extractor, typename HashFunctions, typename Traits, typename KeyTraits>
void HashTable<Key, Value, Extractor, HashFunctions, Traits, KeyTraits>::checkTableConsistency() const
@@ -1993,7 +1993,7 @@
ASSERT(m_tableSize == m_tableSizeMask + 1);
}
-#endif // ASSERT_DISABLED
+#endif // ASSERT_ENABLED
#if CHECK_HASHTABLE_ITERATORS
diff --git a/watch-cli.js b/watch-cli.js
new file mode 100644
index 0000000..49a399d
--- /dev/null
+++ b/watch-cli.js
@@ -0,0 +1,65 @@
+dumpJSONResults = true;
+
+testIterationCount = 15;
+
+testList = [
+ {name: "FlightPlanner"},
+ {name: "UniPoker"},
+ {name: "Air"},
+ {name: "Basic"},
+ {name: "ML", iterations: 7, worstCaseCount: 2},
+ {name: "Babylon"},
+ {name: "cdjs", iterations: 10, worstCaseCount: 2},
+ {name: "first-inspector-code-load"},
+ {name: "multi-inspector-code-load"},
+ {name: "Box2D"},
+ {name: "octane-code-load"},
+ {name: "crypto"},
+ {name: "delta-blue"},
+ {name: "earley-boyer"},
+ {name: "gbemu", iterations: 10, worstCaseCount: 2},
+ {name: "navier-stokes"},
+ {name: "pdfjs"},
+ {name: "raytrace"},
+ {name: "regexp"},
+ {name: "richards"},
+ {name: "splay"},
+ {name: "ai-astar"},
+ {name: "gaussian-blur", iterations: 10, worstCaseCount: 2},
+ {name: "stanford-crypto-aes"},
+ {name: "stanford-crypto-pbkdf2"},
+ {name: "stanford-crypto-sha256"},
+ {name: "json-stringify-inspector"},
+ {name: "json-parse-inspector"},
+ {name: "async-fs", iterations: 8, worstCaseCount: 2},
+ {name: "hash-map", iterations: 12, worstCaseCount: 3},
+ {name: "3d-cube-SP"},
+ {name: "3d-raytrace-SP"},
+ {name: "base64-SP"},
+ {name: "crypto-aes-SP"},
+ {name: "crypto-md5-SP"},
+ {name: "crypto-sha1-SP"},
+ {name: "date-format-tofte-SP"},
+ {name: "date-format-xparb-SP"},
+ {name: "n-body-SP"},
+ {name: "regex-dna-SP"},
+ {name: "string-unpack-code-SP"},
+ {name: "tagcloud-SP"},
+ {name: "chai-wtb", iterations: 5, worstCaseCount: 2},
+ {name: "jshint-wtb", iterations: 5, worstCaseCount: 2},
+ {name: "prepack-wtb", iterations: 5, worstCaseCount: 2}
+];
+
+testIterationCountMap = new Map;
+testWorstCaseCountMap = new Map;
+for (let test of testList) {
+ if (test.iterations)
+ testIterationCountMap.set(test.name, test.iterations);
+ if (test.worstCaseCount)
+ testWorstCaseCountMap.set(test.name, test.worstCaseCount);
+}
+
+
+testList = testList.map(x => x.name);
+
+load("./cli.js");