v8/test/mjsunit/optimized-array-findindex.js
Mike Stanton fb922b1764 [TurboFan] Allow unreliable maps in array builtins
We don't currently inline array builtins if we detect any side effects
between the load of the receiver map and the call to the builtin.
The introduction of a map check allows us to be more permissive.

Bug: v8:7250
Change-Id: I6b3f9243f6506eff45c0d727c47a7e8cb8765640
Reviewed-on: https://chromium-review.googlesource.com/849005
Commit-Queue: Michael Stanton <mvstanton@chromium.org>
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50620}
2018-01-16 14:01:29 +00:00

461 lines
11 KiB
JavaScript

// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --turbo-inline-array-builtins --opt
// Flags: --no-always-opt
// Unknown field access leads to soft-deopt unrelated to findIndex, should still
// lead to correct result.
(() => {
const a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25];
let result = 0;
function eagerDeoptInCalled(deopt) {
return a.findIndex((v, i) => {
if (i === 13 && deopt) {
a.abc = 25;
}
result += v;
return v === 20;
});
}
eagerDeoptInCalled();
eagerDeoptInCalled();
%OptimizeFunctionOnNextCall(eagerDeoptInCalled);
eagerDeoptInCalled();
assertEquals(19, eagerDeoptInCalled(true));
eagerDeoptInCalled();
assertEquals(1050, result);
})();
// Length change detected during loop, must cause properly handled eager deopt.
(() => {
let called_values;
function eagerDeoptInCalled(deopt) {
const a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
called_values = [];
return a.findIndex((v,i) => {
called_values.push(v);
a.length = (i === 5 && deopt) ? 8 : 10;
return v === 9;
});
}
assertEquals(8, eagerDeoptInCalled());
assertArrayEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], called_values);
eagerDeoptInCalled();
%OptimizeFunctionOnNextCall(eagerDeoptInCalled);
assertEquals(8, eagerDeoptInCalled());
assertEquals(-1, eagerDeoptInCalled(true));
assertArrayEquals([1, 2, 3, 4, 5, 6, 7, 8, undefined, undefined],
called_values);
eagerDeoptInCalled();
})();
// Lazy deopt from a callback that changes the input array. Deopt in a callback
// execution that returns true.
(() => {
const a = [1, 2, 3, 4, 5];
function lazyChanger(deopt) {
return a.findIndex((v, i) => {
if (i === 3 && deopt) {
a[3] = 3;
%DeoptimizeNow();
}
return v > 3;
});
}
assertEquals(3, lazyChanger());
lazyChanger();
%OptimizeFunctionOnNextCall(lazyChanger);
assertEquals(3, lazyChanger(true));
assertEquals(4, lazyChanger());
})();
// Lazy deopt from a callback that will always return false and no element is
// found. Verifies the lazy-after-callback continuation builtin.
(() => {
const a = [1, 2, 3, 4, 5];
function lazyChanger(deopt) {
return a.findIndex((v, i) => {
if (i === 3 && deopt) {
%DeoptimizeNow();
}
return false;
});
}
assertEquals(-1, lazyChanger());
lazyChanger();
%OptimizeFunctionOnNextCall(lazyChanger);
assertEquals(-1, lazyChanger(true));
assertEquals(-1, lazyChanger());
})();
// Lazy deopt from a callback that changes the input array. Deopt in a callback
// execution that returns false.
(() => {
const a = [1, 2, 3, 4, 5];
function lazyChanger(deopt) {
return a.findIndex((v, i) => {
if (i === 2 && deopt) {
a[3] = 2;
%DeoptimizeNow();
}
return v > 3;
});
}
assertEquals(3, lazyChanger());
lazyChanger();
%OptimizeFunctionOnNextCall(lazyChanger);
assertEquals(4, lazyChanger(true));
assertEquals(4, lazyChanger());
})();
// Escape analyzed array
(() => {
let result = 0;
function eagerDeoptInCalled(deopt) {
const a_noescape = [0, 1, 2, 3, 4, 5];
a_noescape.findIndex((v, i) => {
result += v | 0;
if (i === 13 && deopt) {
a_noescape.length = 25;
}
return false;
});
}
eagerDeoptInCalled();
eagerDeoptInCalled();
%OptimizeFunctionOnNextCall(eagerDeoptInCalled);
eagerDeoptInCalled();
eagerDeoptInCalled(true);
eagerDeoptInCalled();
assertEquals(75, result);
})();
// Lazy deopt from runtime call from inlined callback function.
(() => {
const a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25];
let result = 0;
function lazyDeopt(deopt) {
a.findIndex((v, i) => {
result += i;
if (i === 13 && deopt) {
%DeoptimizeNow();
}
return false;
});
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
lazyDeopt(true);
lazyDeopt();
assertEquals(1500, result);
})();
// Lazy deopt from runtime call from non-inline callback function.
(() => {
const a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25];
let result = 0;
function lazyDeopt(deopt) {
function callback(v, i) {
result += i;
if (i === 13 && deopt) {
%DeoptimizeNow();
}
return false;
}
%NeverOptimizeFunction(callback);
a.findIndex(callback);
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
lazyDeopt(true);
lazyDeopt();
assertEquals(1500, result);
})();
// Call to a.findIndex is done inside a try-catch block and the callback function
// being called actually throws.
(() => {
const a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25];
let caught = false;
function lazyDeopt(deopt) {
try {
a.findIndex((v, i) => {
if (i === 1 && deopt) {
throw("a");
}
return false;
});
} catch (e) {
caught = true;
}
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
assertDoesNotThrow(() => lazyDeopt(true));
assertTrue(caught);
lazyDeopt();
})();
// Call to a.findIndex is done inside a try-catch block and the callback function
// being called actually throws, but the callback is not inlined.
(() => {
let a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let caught = false;
function lazyDeopt(deopt) {
function callback(v, i) {
if (i === 1 && deopt) {
throw("a");
}
return false;
}
%NeverOptimizeFunction(callback);
try {
a.findIndex(callback);
} catch (e) {
caught = true;
}
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
assertDoesNotThrow(() => lazyDeopt(true));
assertTrue(caught);
lazyDeopt();
})();
// Call to a.findIndex is done inside a try-catch block and the callback function
// being called throws into a deoptimized caller function.
(function TestThrowIntoDeoptimizedOuter() {
const a = [1, 2, 3, 4];
function lazyDeopt(deopt) {
function callback(v, i) {
if (i === 1 && deopt) {
%DeoptimizeFunction(lazyDeopt);
throw "some exception";
}
return v === 3;
}
%NeverOptimizeFunction(callback);
let result = 0;
try {
result = a.findIndex(callback);
} catch (e) {
assertEquals("some exception", e);
result = "nope";
}
return result;
}
assertEquals(2, lazyDeopt(false));
assertEquals(2, lazyDeopt(false));
assertEquals("nope", lazyDeopt(true));
assertEquals("nope", lazyDeopt(true));
%OptimizeFunctionOnNextCall(lazyDeopt);
assertEquals(2, lazyDeopt(false));
assertEquals("nope", lazyDeopt(true));
})();
// An error generated inside the callback includes findIndex in it's
// stack trace.
(() => {
const re = /Array\.findIndex/;
function lazyDeopt(deopt) {
const b = [1, 2, 3];
let result = 0;
b.findIndex((v, i) => {
result += v;
if (i === 1) {
const e = new Error();
assertTrue(re.exec(e.stack) !== null);
}
return false;
});
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
lazyDeopt();
})();
// An error generated inside a non-inlined callback function also
// includes findIndex in it's stack trace.
(() => {
const re = /Array\.findIndex/;
function lazyDeopt(deopt) {
const b = [1, 2, 3];
let did_assert_error = false;
let result = 0;
function callback(v, i) {
result += v;
if (i === 1) {
const e = new Error();
assertTrue(re.exec(e.stack) !== null);
did_assert_error = true;
}
return false;
}
%NeverOptimizeFunction(callback);
b.findIndex(callback);
return did_assert_error;
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
assertTrue(lazyDeopt());
})();
// An error generated inside a recently deoptimized callback function
// includes findIndex in it's stack trace.
(() => {
const re = /Array\.findIndex/;
function lazyDeopt(deopt) {
const b = [1, 2, 3];
let did_assert_error = false;
let result = 0;
b.findIndex((v, i) => {
result += v;
if (i === 1) {
%DeoptimizeNow();
} else if (i === 2) {
const e = new Error();
assertTrue(re.exec(e.stack) !== null);
did_assert_error = true;
}
return false;
});
return did_assert_error;
}
lazyDeopt();
lazyDeopt();
%OptimizeFunctionOnNextCall(lazyDeopt);
assertTrue(lazyDeopt());
})();
// Verify that various exception edges are handled appropriately.
// The thrown Error object should always indicate it was created from
// a findIndex call stack.
(() => {
const re = /Array\.findIndex/;
const a = [1, 2, 3];
let result = 0;
function lazyDeopt() {
a.findIndex((v, i) => {
result += i;
if (i === 1) {
%DeoptimizeFunction(lazyDeopt);
throw new Error();
}
return false;
});
}
assertThrows(() => lazyDeopt());
assertThrows(() => lazyDeopt());
try {
lazyDeopt();
} catch (e) {
assertTrue(re.exec(e.stack) !== null);
}
%OptimizeFunctionOnNextCall(lazyDeopt);
try {
lazyDeopt();
} catch (e) {
assertTrue(re.exec(e.stack) !== null);
}
})();
// Messing with the Array prototype causes deoptimization.
(() => {
const a = [1, 2, 3];
let result = 0;
function prototypeChanged() {
a.findIndex((v, i) => {
result += v;
return false;
});
}
prototypeChanged();
prototypeChanged();
%OptimizeFunctionOnNextCall(prototypeChanged);
prototypeChanged();
a.constructor = {};
prototypeChanged();
assertUnoptimized(prototypeChanged);
assertEquals(24, result);
})();
// Verify holes are replaced with undefined.
(() => {
const a = [1, 2, , 3, 4];
function withHoles() {
const callback_values = [];
a.findIndex(v => {
callback_values.push(v);
return false;
});
return callback_values;
}
withHoles();
withHoles();
%OptimizeFunctionOnNextCall(withHoles);
assertArrayEquals([1, 2, undefined, 3, 4], withHoles());
})();
(() => {
const a = [1.5, 2.5, , 3.5, 4.5];
function withHoles() {
const callback_values = [];
a.findIndex(v => {
callback_values.push(v);
return false;
});
return callback_values;
}
withHoles();
withHoles();
%OptimizeFunctionOnNextCall(withHoles);
assertArrayEquals([1.5, 2.5, undefined, 3.5, 4.5], withHoles());
})();
// Ensure that we handle side-effects between load and call.
(() => {
function side_effect(a, b) { if (b) a.foo = 3; return a; }
%NeverOptimizeFunction(side_effect);
function unreliable(a, b) {
return a.findIndex(x => false, side_effect(a, b));
}
let a = [1, 2, 3];
unreliable(a, false);
unreliable(a, false);
%OptimizeFunctionOnNextCall(unreliable);
unreliable(a, false);
// Now actually do change the map.
unreliable(a, true);
})();
// Handle callback is not callable.
(() => {
const a = [1, 2, 3, 4, 5];
function notCallable() {
return a.findIndex(undefined);
}
assertThrows(notCallable, TypeError);
try { notCallable(); } catch(e) { }
%OptimizeFunctionOnNextCall(notCallable);
assertThrows(notCallable, TypeError);
})();