// 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 --expose-gc --turbo-inline-array-builtins // Flags: --no-lazy-feedback-allocation // TODO(v8:10195): Fix these tests s.t. we assert deoptimization occurs when // expected (e.g. in a %DeoptimizeNow call), then remove // --no-lazy-feedback-allocation. var a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 0, 0 ]; var b = [ 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 ]; var c = [ 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 ]; // Unknown field access leads to soft-deopt unrelated to forEach, should still // lead to correct result. (function() { var result = 0; var eagerDeoptInCalled = function(deopt) { var sum = function(v, i, o) { result += v; if (i == 13 && deopt) { a.abc = 25; } }; a.forEach(sum); }; %PrepareFunctionForOptimization(eagerDeoptInCalled); eagerDeoptInCalled(); eagerDeoptInCalled(); %OptimizeFunctionOnNextCall(eagerDeoptInCalled); eagerDeoptInCalled(); eagerDeoptInCalled(true); eagerDeoptInCalled(); assertEquals(1500, result); })(); // Length change detected during loop, must cause properly handled eager deopt. (function() { var result = 0; var eagerDeoptInCalled = function(deopt) { var sum = function(v, i, o) { result += v; a.length = i == 13 && deopt ? 25 : 27; }; a.forEach(sum); }; %PrepareFunctionForOptimization(eagerDeoptInCalled); eagerDeoptInCalled(); eagerDeoptInCalled(); %OptimizeFunctionOnNextCall(eagerDeoptInCalled); eagerDeoptInCalled(); eagerDeoptInCalled(true); eagerDeoptInCalled(); assertEquals(1500, result); })(); // Escape analyzed array (function() { var result = 0; var eagerDeoptInCalled = function(deopt) { var a_noescape = [0, 1, 2, 3, 4, 5]; var sum = function(v, i, o) { result += v; if (i == 13 && deopt) { a_noescape.length = 25; } }; a_noescape.forEach(sum); }; %PrepareFunctionForOptimization(eagerDeoptInCalled); eagerDeoptInCalled(); eagerDeoptInCalled(); %OptimizeFunctionOnNextCall(eagerDeoptInCalled); eagerDeoptInCalled(); eagerDeoptInCalled(true); eagerDeoptInCalled(); assertEquals(75, result); })(); // Escape analyzed array where sum function isn't inlined, forcing a lazy deopt // with GC that relies on the stashed-away return result fro the lazy deopt // being properly stored in a place on the stack that gets GC'ed. (function() { var result = 0; var lazyDeopt = function(deopt) { var b = [1, 2, 3]; var sum = function(v, i, o) { result += i; if (i == 1 && deopt) { %DeoptimizeFunction(lazyDeopt); } gc(); gc(); }; %NeverOptimizeFunction(sum); b.forEach(sum); }; %PrepareFunctionForOptimization(lazyDeopt); lazyDeopt(); lazyDeopt(); %OptimizeFunctionOnNextCall(lazyDeopt); lazyDeopt(); lazyDeopt(true); lazyDeopt(); })(); // Lazy deopt from runtime call from inlined callback function. (function() { var result = 0; var lazyDeopt = function(deopt) { var sum = function(v, i, o) { result += i; if (i == 13 && deopt) { %DeoptimizeNow(); } }; b.forEach(sum); }; %PrepareFunctionForOptimization(lazyDeopt); lazyDeopt(); lazyDeopt(); %OptimizeFunctionOnNextCall(lazyDeopt); lazyDeopt(); lazyDeopt(true); lazyDeopt(); assertEquals(1500, result); })(); // Lazy deopt from runtime call from non-inline callback function. (function() { var result = 0; var lazyDeopt = function(deopt) { var sum = function(v, i, o) { result += i; if (i == 13 && deopt) { %DeoptimizeNow(); } }; %NeverOptimizeFunction(sum); b.forEach(sum); }; %PrepareFunctionForOptimization(lazyDeopt); lazyDeopt(); lazyDeopt(); %OptimizeFunctionOnNextCall(lazyDeopt); lazyDeopt(); lazyDeopt(true); lazyDeopt(); assertEquals(1500, result); })(); (function() { var result = 0; var lazyDeopt = function(deopt) { var sum = function(v, i, o) { result += i; if (i == 13 && deopt) { %DeoptimizeNow(); gc(); gc(); gc(); } }; c.forEach(sum); }; %PrepareFunctionForOptimization(lazyDeopt); lazyDeopt(); lazyDeopt(); %OptimizeFunctionOnNextCall(lazyDeopt); lazyDeopt(); lazyDeopt(true); lazyDeopt(); assertEquals(1500, result); })(); // Call to a.forEach is done inside a try-catch block and the callback function // being called actually throws. (function() { var caught = false; var result = 0; var lazyDeopt = function(deopt) { var sum = function(v, i, o) { result += i; if (i == 1 && deopt) { throw 'a'; } }; try { c.forEach(sum); } catch (e) { caught = true; } }; %PrepareFunctionForOptimization(lazyDeopt); lazyDeopt(); lazyDeopt(); %OptimizeFunctionOnNextCall(lazyDeopt); lazyDeopt(); assertDoesNotThrow(lazyDeopt.bind(this, true)); assertTrue(caught); lazyDeopt(); })(); // Call to a.forEach is done inside a try-catch block and the callback function // being called actually throws, but the callback is not inlined. (function() { var caught = false; var result = 0; var lazyDeopt = function(deopt) { var sum = function(v, i, o) { result += i; if (i == 1 && deopt) { throw 'a'; } }; %NeverOptimizeFunction(sum); try { c.forEach(sum); } catch (e) { caught = true; } }; %PrepareFunctionForOptimization(lazyDeopt); lazyDeopt(); lazyDeopt(); %OptimizeFunctionOnNextCall(lazyDeopt); lazyDeopt(); assertDoesNotThrow(lazyDeopt.bind(this, true)); assertTrue(caught); lazyDeopt(); })(); // Call to a.forEach is done inside a try-catch block and the callback function // being called throws into a deoptimized caller function. (function TestThrowIntoDeoptimizedOuter() { var a = [1, 2, 3, 4]; var lazyDeopt = function(deopt) { var sum = function(v, i, o) { result += v; if (i == 1 && deopt) { %DeoptimizeFunction(lazyDeopt); throw "some exception"; } }; %NeverOptimizeFunction(sum); var result = 0; try { a.forEach(sum); } catch (e) { assertEquals('some exception', e); result += 100; } return result; }; ; %PrepareFunctionForOptimization(lazyDeopt); assertEquals(10, lazyDeopt(false)); assertEquals(10, lazyDeopt(false)); assertEquals(103, lazyDeopt(true)); assertEquals(103, lazyDeopt(true)); %OptimizeFunctionOnNextCall(lazyDeopt); assertEquals(10, lazyDeopt(false)); assertEquals(103, lazyDeopt(true)); })(); (function() { var re = /Array\.forEach/; var lazyDeopt = function foobar(deopt) { var b = [1, 2, 3]; var result = 0; var sum = function(v, i, o) { result += v; if (i == 1) { var e = new Error(); assertTrue(re.exec(e.stack) !== null); } }; var o = [1, 2, 3]; b.forEach(sum); }; %PrepareFunctionForOptimization(lazyDeopt); lazyDeopt(); lazyDeopt(); %OptimizeFunctionOnNextCall(lazyDeopt); lazyDeopt(); })(); (function() { var re = /Array\.forEach/; var lazyDeopt = function(deopt) { var b = [1, 2, 3]; var result = 0; var sum = function(v, i, o) { result += v; if (i == 1) { var e = new Error(); assertTrue(re.exec(e.stack) !== null); } }; %NeverOptimizeFunction(sum); var o = [1, 2, 3]; b.forEach(sum); }; %PrepareFunctionForOptimization(lazyDeopt); lazyDeopt(); lazyDeopt(); %OptimizeFunctionOnNextCall(lazyDeopt); lazyDeopt(); })(); (function() { var re = /Array\.forEach/; var lazyDeopt = function(deopt) { var b = [1, 2, 3]; var result = 0; var sum = function(v, i, o) { result += v; if (i == 1) { %DeoptimizeNow(); } else if (i == 2) { var e = new Error(); assertTrue(re.exec(e.stack) !== null); } }; var o = [1, 2, 3]; b.forEach(sum); }; %PrepareFunctionForOptimization(lazyDeopt); lazyDeopt(); lazyDeopt(); %OptimizeFunctionOnNextCall(lazyDeopt); lazyDeopt(); })(); (function() { var re = /Array\.forEach/; var a = [1, 2, 3]; var result = 0; var lazyDeopt = function() { var sum = function(v, i, o) { result += i; if (i == 1) { %DeoptimizeFunction(lazyDeopt); throw new Error(); } }; a.forEach(sum); }; %PrepareFunctionForOptimization(lazyDeopt); 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); } })(); // Verify holes are skipped. (() => { const a = [1, 2, , 3, 4]; function withHoles() { const callback_values = []; a.forEach(v => { callback_values.push(v); }); return callback_values; }; %PrepareFunctionForOptimization(withHoles); withHoles(); withHoles(); %OptimizeFunctionOnNextCall(withHoles); assertArrayEquals([1, 2, 3, 4], withHoles()); })(); (() => { const a = [1.5, 2.5, , 3.5, 4.5]; function withHoles() { const callback_values = []; a.forEach(v => { callback_values.push(v); }); return callback_values; }; %PrepareFunctionForOptimization(withHoles); withHoles(); withHoles(); %OptimizeFunctionOnNextCall(withHoles); assertArrayEquals([1.5, 2.5, 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) { let sum = 0; return a.forEach(x => sum += x, side_effect(a, b)); }; %PrepareFunctionForOptimization(unreliable); 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); })();