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