Add branch for JetStream v2.1

This is a copy of JetStream v2.1 as of https://commits.webkit.org/254294@main
This commit is contained in:
Keith Miller 2024-11-18 13:50:55 -05:00
parent 60cdba17be
commit 1466de09a5
Failed to extract signature
16 changed files with 1441 additions and 145 deletions

View file

@ -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 @@ a.button {
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;

View file

@ -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 RAMification === "undefined")
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 @@ this.currentReject = null;
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 @@ const fileLoader = (function() {
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 @@ const fileLoader = (function() {
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 @@ class Driver {
await updateUI();
try {
await benchmark.run();
} catch(e) {
JetStream.reportError(benchmark);
@ -182,6 +253,16 @@ class Driver {
}
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 @@ class Driver {
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 @@ class Driver {
};
for (let script of scripts)
globalObject.loadString(script);
return globalObject;
return isD8 ? realm : globalObject;
}
var magic = document.getElementById("magic");
@ -286,6 +401,7 @@ class Driver {
}
async initialize() {
await this.prefetchResourcesForBrowser();
await this.fetchResources();
this.prepareToRun();
if (isInBrowser && window.location.search == '?report=true') {
@ -293,9 +409,40 @@ class Driver {
}
}
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 @@ class Driver {
}
}
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 @@ class Driver {
"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 @@ class Benchmark {
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 @@ class Benchmark {
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 @@ class Benchmark {
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 @@ class Benchmark {
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 @@ class DefaultBenchmark extends Benchmark {
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 @@ class DefaultBenchmark extends Benchmark {
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 @@ class WSLBenchmark extends Benchmark {
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 @@ class WasmBenchmark extends Benchmark {
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 runWorkerTests = !!isInBrowser;
let runSeaMonster = true;
let runCodeLoad = true;
let runWasm = true;
if (typeof WebAssembly === "undefined")
runWasm = false;
if (false) {
runOctane = false;

View file

@ -1,5 +1 @@
class Benchmark {
runIteration() {
print("Hello world!");
}
}
print("Hello world!");

View file

@ -8,16 +8,5 @@ function firstWhere(list, fn) {
}
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)

View file

@ -2,17 +2,7 @@ var n = 40000;
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;
}

537
LuaJSFight/richards.js Normal file
View file

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

View file

@ -2,17 +2,7 @@ var n = 40000;
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;
}

376
RAMification.py Normal file
View file

@ -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())

View file

@ -43,8 +43,9 @@ class Benchmark {
{
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]);
}
}
}

0
__init__.py Normal file
View file

4
cli.js
View file

@ -28,6 +28,10 @@ console = {
log: print
}
const isD8 = typeof Realm !== "undefined";
if (isD8)
readFile = read;
if (typeof testList === "undefined")
testList = undefined;

View file

@ -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>

View file

@ -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>

45
wasm-cli.js Normal file
View file

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

View file

@ -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 @@ struct CustomHashTraits : public GenericHashTraits<T> {
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 @@ struct CustomHashTraits : public GenericHashTraits<T> {
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 @@ struct CustomHashTraits : public GenericHashTraits<T> {
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 @@ struct CustomHashTraits : public GenericHashTraits<T> {
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 @@ struct CustomHashTraits : public GenericHashTraits<T> {
ASSERT(m_tableSize == m_tableSizeMask + 1);
}
#endif // ASSERT_DISABLED
#endif // ASSERT_ENABLED
#if CHECK_HASHTABLE_ITERATORS

65
watch-cli.js Normal file
View file

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