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