Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions JSTests/stress/for-of-array-index-in-frame-aliasing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//@ runDefault
//@ runDefault("--useUnboxedFastArrayIteration=0")
//@ runDefault("--useUnboxedFastArrayIteration=1")
//@ runDefault("--useUnboxedFastArrayIteration=1", "--useJIT=0")
//@ runDefault("--useUnboxedFastArrayIteration=1", "--useDFGJIT=0", "--thresholdForJITAfterWarmUp=10", "--thresholdForJITSoon=10")
//@ runDefault("--useUnboxedFastArrayIteration=1", "--useConcurrentJIT=0", "--thresholdForJITAfterWarmUp=10", "--thresholdForJITSoon=10", "--thresholdForOptimizeAfterWarmUp=20", "--thresholdForOptimizeAfterLongWarmUp=20", "--thresholdForOptimizeSoon=20", "--thresholdForFTLOptimizeAfterWarmUp=50", "--thresholdForFTLOptimizeSoon=50")
//@ runDefault("--useUnboxedFastArrayIteration=1", "--useBytecodeOptimizer=1")

// While an Array is destructured or looped over without an iterator object, the Array is read back from a register at
// every step. Nothing the program does to the binding the Array came from (parameters through `arguments`, eval, with,
// closures, assignments in default values and in the loop body) may change which Array is being iterated.

function shouldBe(actual, expected, message) {
if (actual !== expected)
throw new Error((message ? message + ": " : "") + "expected " + expected + " but got " + actual);
}

const other = ["x", "y", "z"];

// Sloppy function, simple parameter list: `arguments` is mapped to the parameters.
function sloppySimple(p) {
var [a, b = (arguments[0] = other, p = other, "d"), c] = p;
return [a, b, c, p === other].join();
}
function sloppySimpleLoop(p) {
var seen = [];
for (var x of p) { seen.push(x); arguments[0] = other; p = other; }
return seen.join() + "|" + (p === other);
}
// Non-simple parameter list: `arguments` is not mapped, the pattern binds straight from the argument.
function sloppyPattern([a, b = (arguments[0] = other, "d"), c]) {
return [a, b, c, arguments[0] === other].join();
}
function sloppyPatternDefault([a, b = (arguments[0] = other, "d"), c] = other) {
return [a, b, c].join();
}
function strictSimple(p) {
"use strict";
var [a, b = (arguments[0] = other, p = other, "d"), c] = p;
return [a, b, c, p === other].join();
}
function viaEval(p) {
var [a, b = (eval("p = other"), "d"), c] = p;
return [a, b, c, p === other].join();
}
function viaEvalLoop(p) {
var seen = [];
for (var x of p) { seen.push(x); eval("p = other; var x2 = 1"); }
return seen.join();
}
function viaWith(scope) {
with (scope) {
var [a, b = (p = other, "d"), c] = p;
var seen = [];
for (var x of q) { seen.push(x); q = other; scope.q = other; }
}
return [a, b, c].join() + "|" + seen.join() + "|" + (scope.p === other) + (scope.q === other);
}
function viaClosure(p) {
function set() { p = other; }
var [a, b = (set(), "d"), c] = p;
var seen = [];
for (var x of p = [7, 8, 9]) { seen.push(x); set(); }
return [a, b, c].join() + "|" + seen.join();
}
function selfAssign(p) {
[p, p] = p;
return String(p);
}
function selfAssignLoop(p) {
var seen = [];
for (p of p) seen.push(p);
return seen.join() + "|" + p;
}
function nestedSame(p) {
var [[a, b], [c, d] = p, e = (p = other)] = p;
return [a, b, c, d, e === other].join();
}
function catchParameter(p) {
try { throw p; } catch ([a, b = (p = other, "d"), c]) { return [a, b, c].join(); }
}
var globalArray;
function viaGlobal() {
var seen = [];
for (var x of globalArray) { seen.push(x); globalArray = other; }
var [a, b = (globalArray = [0], "d")] = globalArray;
return seen.join() + "|" + a + b;
}
function arrowWithRest(...args) {
var f = ([a, b = (args[0] = other, args = other, "d"), c]) => [a, b, c].join();
return f(args[0]);
}

// (Two evals and a with in every round.)
for (var i = 0; i < Math.min(testLoopCount, 2500); i++) {
shouldBe(sloppySimple([1, , 3]), "1,d,3,true");
shouldBe(sloppySimple([1, 2, 3]), "1,2,3,false");
shouldBe(sloppySimpleLoop([1, 2, 3]), "1,2,3|true");
shouldBe(sloppyPattern([1, , 3]), "1,d,3,true");
shouldBe(sloppyPatternDefault([1, , 3]), "1,d,3");
shouldBe(sloppyPatternDefault(), "x,y,z");
shouldBe(strictSimple([1, , 3]), "1,d,3,true");
shouldBe(viaEval([1, , 3]), "1,d,3,true");
shouldBe(viaEvalLoop([1, 2, 3]), "1,2,3");
shouldBe(viaWith({ p: [1, , 3], q: [4, 5, 6] }), "1,d,3|4,5,6|truetrue");
shouldBe(viaClosure([1, , 3]), "1,d,3|7,8,9");
shouldBe(selfAssign([1, 2, 3]), "2");
shouldBe(selfAssignLoop([1, 2, 3]), "1,2,3|3");
shouldBe(nestedSame([[1, 2], , ,]), "1,2,1,2,,true");
shouldBe(catchParameter([1, , 3]), "1,d,3");
globalArray = [1, 2, 3];
shouldBe(viaGlobal(), "1,2,3|xy");
shouldBe(arrowWithRest([1, , 3]), "1,d,3");
}
119 changes: 119 additions & 0 deletions JSTests/stress/for-of-array-index-in-frame-close-realms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//@ runDefault
//@ runDefault("--useUnboxedFastArrayIteration=0")
//@ runDefault("--useUnboxedFastArrayIteration=1")
//@ runDefault("--useUnboxedFastArrayIteration=1", "--useJIT=0")
//@ runDefault("--useUnboxedFastArrayIteration=1", "--useDFGJIT=0", "--thresholdForJITAfterWarmUp=10", "--thresholdForJITSoon=10")
//@ runDefault("--useUnboxedFastArrayIteration=1", "--useConcurrentJIT=0", "--thresholdForJITAfterWarmUp=10", "--thresholdForJITSoon=10", "--thresholdForOptimizeAfterWarmUp=20", "--thresholdForOptimizeAfterLongWarmUp=20", "--thresholdForOptimizeSoon=20", "--thresholdForFTLOptimizeAfterWarmUp=50", "--thresholdForFTLOptimizeSoon=50")
//@ runDefault("--useUnboxedFastArrayIteration=1", "--forceOSRExitToLLInt=1", "--useFTLJIT=0", "--useConcurrentJIT=0", "--thresholdForJITAfterWarmUp=10", "--thresholdForOptimizeAfterWarmUp=20", "--thresholdForOptimizeAfterLongWarmUp=20")

// for-of-array-index-in-frame-close.js makes IteratorClose observable from inside a loop that was opened without an iterator
// object once: a realm's watchpoints do not come back, and from then on it opens its loops with an Array Iterator object. Here
// every way out of a loop, and "return" installed from the default value of an element in the middle of a pattern (with and
// without a throw after it), gets a realm of its own that has done nothing but run the function a few times, for each of the three
// prototypes "return" can be installed on. "return" then stays, and the next call finds it there from the start.

function shouldBe(actual, expected, message) {
if (actual !== expected)
throw new Error((message ? message + ": " : "") + "expected " + expected + " but got " + actual);
}

const realmSource = `
const ArrayIteratorPrototype = Object.getPrototypeOf([][Symbol.iterator]());
const IteratorPrototype = Object.getPrototypeOf(ArrayIteratorPrototype);
const originalNext = ArrayIteratorPrototype.next;
noDFG(originalNext); // It gets a handful of calls in each of these realms.
const holders = [ArrayIteratorPrototype, IteratorPrototype, Object.prototype];
const outError = new Error("out");
const defaultError = new Error("dflt");
let log = [];
let seenIterators = [];
function installReturn(where) {
where.return = function () {
// The index is observable through a next() on the iterator that was handed to us.
let step = originalNext.call(this);
log.push("return " + Object.prototype.toString.call(this) + " " + (Object.getPrototypeOf(this) === ArrayIteratorPrototype) + " " + JSON.stringify(step));
seenIterators.push(this);
return {};
};
}

function breakOut(array, at, hook) { let seen = []; for (let x of array) { seen.push(x); if (x === at) { hook(); break; } } return seen.join(); }
function returnOut(array, at, hook) { let seen = []; for (let x of array) { seen.push(x); if (x === at) { hook(); return seen.join(); } } return seen.join(); }
function throwOut(array, at, hook) { let seen = []; try { for (let x of array) { seen.push(x); if (x === at) { hook(); throw outError; } } } catch (e) { seen.push(e.message); } return seen.join(); }
function continueOuter(array, at, hook) { let seen = []; outer: for (let i = 0; i < 2; i++) { for (let x of array) { seen.push(x); if (x === at) { hook(); continue outer; } } } return seen.join(); }
function breakOuter(array, at, hook) { let seen = []; outer: for (let i = 0; i < 2; i++) { for (let x of array) { seen.push(x); if (x === at) { hook(); break outer; } } } return seen.join(); }
function finallyOut(array, at, hook) { let seen = []; try { for (let x of array) { seen.push(x); if (x === at) { hook(); return seen.join(); } } } finally { seen.push("finally"); log.push("finally " + seen.join()); } return seen.join(); }
function runToEnd(array, at, hook) { let seen = []; for (let x of array) { seen.push(x); if (x === at) hook(); } return seen.join(); }
function nested(array, at, hook) { let seen = []; for (let x of array) { for (let y of array) { seen.push(x * 10 + y); if (y === at) { hook(); break; } } if (x === at) break; } return seen.join(); }
function destructure2(array, at, hook) { hook(); let [a, b] = array; return a + "," + b; }
function destructureEmpty(array, at, hook) { hook(); let [] = array; return ""; }
function destructureRest(array, at, hook) { hook(); let [a, ...r] = array; return a + "," + r.join(); }
function throwDefault() { throw defaultError; }
function destructureDefault(array, at, hook) { let [a, b = (hook(), "d")] = array; return a + "," + b; }
function destructureThrow(array, at, hook) { try { let [a, b = (hook(), throwDefault())] = array; } catch (e) { return e.message; } return "no throw"; }

function run(f, array, at, holder, calls) {
const quietHooks = [() => { }, () => { }];
for (let i = 0; i < calls; i++)
f(array.slice(), at, quietHooks[i & 1]);
let results = { quiet: log };
log = [];
results.result = f(array.slice(), at, () => installReturn(holders[holder]));
results.log = log;
results.iterators = seenIterators.length;
results.distinctIterators = new Set(seenIterators).size;
results.arrayIterators = seenIterators.every(iterator => typeof iterator === "object" && Object.getPrototypeOf(iterator) === ArrayIteratorPrototype);
log = [];
results.resultAfter = f(array.slice(), at, quietHooks[0]);
results.logAfter = log;
return results;
}
`;

const afterTwo = 'return [object Array Iterator] true {"value":3,"done":false}';
const secondMissing = "[1, undefined, 3, 4]";
let scenarios = [
["breakOut", 2, "1,2", afterTwo],
["returnOut", 3, "1,2,3", 'return [object Array Iterator] true {"value":4,"done":false}'],
["throwOut", 1, "1,out", 'return [object Array Iterator] true {"value":2,"done":false}'],
["continueOuter", 4, "1,2,3,4,1,2,3,4", 'return [object Array Iterator] true {"done":true}|return [object Array Iterator] true {"done":true}'],
["breakOuter", 2, "1,2", afterTwo],
["finallyOut", 2, "1,2", afterTwo + "|finally 1,2,finally"],
["runToEnd", 2, "1,2,3,4", ""],
["nested", 2, "11,12,21,22", [afterTwo, afterTwo, afterTwo].join("|")],
// These run their hook before the pattern is opened: opening sees an observable protocol.
["destructure2", 0, "1,2", afterTwo],
["destructureEmpty", 0, "", 'return [object Array Iterator] true {"value":1,"done":false}'],
["destructureRest", 0, "1,2,3,4", ""],
// And these from the default value of the second element: the iterator object has to appear half way through the pattern.
["destructureDefault", 0, "1,d", afterTwo, secondMissing],
["destructureThrow", 0, "dflt", afterTwo, secondMissing],
];

// Enough calls for the Baseline JIT where the run line lowers its threshold. What makes the Array Iterator object is the
// same function in every tier; the other test has the optimized frames.
const calls = Math.min(testLoopCount, 12);

function relevant(name, log) { return (name === "finallyOut" ? log : log.filter(s => s.startsWith("return"))).join("|"); }

function inARealmOfItsOwn(name, at, expected, expectedLog, array, holder) {
let realm = createGlobalObject();
realm.eval(realmSource);
let results = realm.eval(`run(${name}, ${array}, ${at}, ${holder}, ${calls})`);
let message = name + ", holder " + holder;
shouldBe(relevant("", results.quiet), "", message + " quiet");
shouldBe(results.result, expected, message);
shouldBe(relevant(name, results.log), expectedLog, message);
shouldBe(results.iterators, results.log.filter(s => s.startsWith("return")).length, message + " iterators");
shouldBe(results.distinctIterators, results.iterators, message + " distinct iterators");
shouldBe(results.arrayIterators, true, message);
shouldBe(results.resultAfter, expected, message + " after");
shouldBe(relevant(name, results.logAfter), expectedLog, message + " after");
}
// This only drives the other realms.
noDFG(inARealmOfItsOwn);

for (let [name, at, expected, expectedLog, array = "[1, 2, 3, 4]"] of scenarios) {
for (let holder = 0; holder < 3; holder++)
inARealmOfItsOwn(name, at, expected, expectedLog, array, holder);
}
Loading
Loading