| // 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: --opt --no-always-opt |
| |
| 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 map, should still |
| // lead to correct result. |
| (function() { |
| var result = 0; |
| var eagerDeoptInCalled = function(deopt) { |
| var callback = function(v,i,o) { |
| result += v; |
| if (i == 13 && deopt) { |
| a.abc = 25; |
| } |
| return v; |
| } |
| a.map(callback); |
| } |
| 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 callback = function(v,i,o) { |
| result += v; |
| a.length = (i == 13 && deopt) ? 25 : 27; |
| return v; |
| } |
| a.map(callback); |
| } |
| 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 callback = function(v,i,o) { |
| result += v; |
| if (i == 13 && deopt) { |
| a_noescape.length = 25; |
| } |
| return v; |
| } |
| a_noescape.map(callback); |
| } |
| eagerDeoptInCalled(); |
| eagerDeoptInCalled(); |
| %OptimizeFunctionOnNextCall(eagerDeoptInCalled); |
| eagerDeoptInCalled(); |
| eagerDeoptInCalled(true); |
| eagerDeoptInCalled(); |
| assertEquals(75, result); |
| })(); |
| |
| // Escape analyzed array where callback 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 callback = function(v,i,o) { |
| result += i; |
| if (i == 1 && deopt) { |
| %DeoptimizeFunction(lazyDeopt); |
| } |
| gc(); gc(); |
| return v; |
| }; |
| %NeverOptimizeFunction(callback); |
| b.map(callback); |
| } |
| lazyDeopt(); |
| lazyDeopt(); |
| %OptimizeFunctionOnNextCall(lazyDeopt); |
| lazyDeopt(); |
| lazyDeopt(true); |
| lazyDeopt(); |
| })(); |
| |
| // Escape analyzed array where callback function isn't inlined, forcing a lazy |
| // deopt. Check that the result of the callback function is passed correctly |
| // to the lazy deopt and that the final result of map is as expected. |
| (function() { |
| var lazyDeopt = function(deopt) { |
| var b = [1,2,3]; |
| var callback = function(v,i,o) { |
| if (i == 1 && deopt) { |
| %DeoptimizeFunction(lazyDeopt); |
| } |
| return 2 * v; |
| }; |
| %NeverOptimizeFunction(callback); |
| return b.map(callback); |
| } |
| assertEquals([2,4,6], lazyDeopt()); |
| assertEquals([2,4,6], lazyDeopt()); |
| %OptimizeFunctionOnNextCall(lazyDeopt); |
| assertEquals([2,4,6], lazyDeopt(true)); |
| })(); |
| |
| // Lazy deopt from runtime call from inlined callback function. |
| (function() { |
| var result = 0; |
| var lazyDeopt = function(deopt) { |
| var callback = function(v,i,o) { |
| result += i; |
| if (i == 13 && deopt) { |
| %DeoptimizeNow(); |
| } |
| return v; |
| } |
| b.map(callback); |
| } |
| 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 callback = function(v,i,o) { |
| result += i; |
| if (i == 13 && deopt) { |
| %DeoptimizeNow(); |
| } |
| return v; |
| }; |
| %NeverOptimizeFunction(callback); |
| b.map(callback); |
| } |
| lazyDeopt(); |
| lazyDeopt(); |
| %OptimizeFunctionOnNextCall(lazyDeopt); |
| lazyDeopt(); |
| lazyDeopt(true); |
| lazyDeopt(); |
| assertEquals(1500, result); |
| })(); |
| |
| (function() { |
| var result = 0; |
| var lazyDeopt = function(deopt) { |
| var callback = function(v,i,o) { |
| result += i; |
| if (i == 13 && deopt) { |
| %DeoptimizeNow(); |
| gc(); |
| gc(); |
| gc(); |
| } |
| return v; |
| } |
| c.map(callback); |
| } |
| lazyDeopt(); |
| lazyDeopt(); |
| %OptimizeFunctionOnNextCall(lazyDeopt); |
| lazyDeopt(); |
| lazyDeopt(true); |
| lazyDeopt(); |
| assertEquals(1500, result); |
| })(); |
| |
| // Call to a.map 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 callback = function(v,i,o) { |
| result += i; |
| if (i == 1 && deopt) { |
| throw("a"); |
| } |
| return v; |
| } |
| try { |
| c.map(callback); |
| } catch (e) { |
| caught = true; |
| } |
| } |
| lazyDeopt(); |
| lazyDeopt(); |
| %OptimizeFunctionOnNextCall(lazyDeopt); |
| lazyDeopt(); |
| assertDoesNotThrow(lazyDeopt.bind(this, true)); |
| assertTrue(caught); |
| lazyDeopt(); |
| })(); |
| |
| // Call to a.map 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 callback = function(v,i,o) { |
| result += i; |
| if (i == 1 && deopt) { |
| throw("a"); |
| } |
| return v; |
| }; |
| %NeverOptimizeFunction(callback); |
| try { |
| c.map(callback); |
| } catch (e) { |
| caught = true; |
| } |
| } |
| lazyDeopt(); |
| lazyDeopt(); |
| %OptimizeFunctionOnNextCall(lazyDeopt); |
| lazyDeopt(); |
| assertDoesNotThrow(lazyDeopt.bind(this, true)); |
| assertTrue(caught); |
| lazyDeopt(); |
| })(); |
| |
| // Call to a.map 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 callback = function(v,i,o) { |
| if (i == 1 && deopt) { |
| %DeoptimizeFunction(lazyDeopt); |
| throw "some exception"; |
| } |
| return 2 * v; |
| }; |
| %NeverOptimizeFunction(callback); |
| var result = 0; |
| try { |
| result = a.map(callback); |
| } catch (e) { |
| assertEquals("some exception", e) |
| result = "nope"; |
| } |
| return result; |
| } |
| assertEquals([2,4,6,8], lazyDeopt(false)); |
| assertEquals([2,4,6,8], lazyDeopt(false)); |
| assertEquals("nope", lazyDeopt(true)); |
| assertEquals("nope", lazyDeopt(true)); |
| %OptimizeFunctionOnNextCall(lazyDeopt); |
| assertEquals([2,4,6,8], lazyDeopt(false)); |
| assertEquals("nope", lazyDeopt(true)); |
| })(); |
| |
| (function() { |
| var re = /Array\.map/; |
| var lazyDeopt = function(deopt) { |
| var b = [1,2,3]; |
| var result = 0; |
| var callback = function(v,i,o) { |
| result += v; |
| if (i == 1) { |
| var e = new Error(); |
| assertTrue(re.exec(e.stack) !== null); |
| } |
| return v; |
| }; |
| var o = [1,2,3]; |
| b.map(callback); |
| } |
| lazyDeopt(); |
| lazyDeopt(); |
| %OptimizeFunctionOnNextCall(lazyDeopt); |
| lazyDeopt(); |
| })(); |
| |
| (function() { |
| var re = /Array\.map/; |
| var lazyDeopt = function(deopt) { |
| var b = [1,2,3]; |
| var result = 0; |
| var callback = function(v,i,o) { |
| result += v; |
| if (i == 1) { |
| var e = new Error(); |
| assertTrue(re.exec(e.stack) !== null); |
| } |
| return v; |
| }; |
| %NeverOptimizeFunction(callback); |
| var o = [1,2,3]; |
| b.map(callback); |
| } |
| lazyDeopt(); |
| lazyDeopt(); |
| %OptimizeFunctionOnNextCall(lazyDeopt); |
| lazyDeopt(); |
| })(); |
| |
| (function() { |
| var re = /Array\.map/; |
| var lazyDeopt = function(deopt) { |
| var b = [1,2,3]; |
| var result = 0; |
| var callback = function(v,i,o) { |
| result += v; |
| if (i == 1) { |
| %DeoptimizeNow(); |
| } else if (i == 2) { |
| var e = new Error(); |
| assertTrue(re.exec(e.stack) !== null); |
| } |
| return v; |
| }; |
| var o = [1,2,3]; |
| b.map(callback); |
| } |
| lazyDeopt(); |
| lazyDeopt(); |
| %OptimizeFunctionOnNextCall(lazyDeopt); |
| lazyDeopt(); |
| })(); |
| |
| (function() { |
| var re = /Array\.map/; |
| var a = [1,2,3]; |
| var result = 0; |
| var lazyDeopt = function() { |
| var callback = function(v,i,o) { |
| result += i; |
| if (i == 1) { |
| %DeoptimizeFunction(lazyDeopt); |
| throw new Error(); |
| } |
| return v; |
| }; |
| a.map(callback); |
| } |
| 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 that we remain in optimized code despite transitions in the output |
| // array. |
| (function() { |
| var result = 0; |
| var to_double = function() { |
| var callback = function(v,i,o) { |
| result += v; |
| if (i < 5) { |
| // First transition the output array to PACKED_DOUBLE_ELEMENTS. |
| return v + 0.5; |
| } else { |
| // Then return smi values and make sure they can live in the double |
| // array. |
| return v; |
| } |
| } |
| return c.map(callback); |
| } |
| to_double(); |
| to_double(); |
| %OptimizeFunctionOnNextCall(to_double); |
| var output = to_double(); |
| assertTrue(%HasDoubleElements(output)); |
| assertEquals(1.5, output[0]); |
| assertEquals(6, output[5]); |
| assertEquals(975, result); |
| assertOptimized(to_double); |
| })(); |
| |
| (function() { |
| var result = 0; |
| var to_fast = function() { |
| var callback = function(v,i,o) { |
| result += v; |
| if (i < 5) { |
| // First transition the output array to PACKED_DOUBLE_ELEMENTS. |
| return v + 0.5; |
| } else if (i < 10) { |
| // Then return smi values and make sure they can live in the double |
| // array. |
| return v; |
| } else { |
| // Later, to PACKED_ELEMENTS. |
| return v + 'hello'; |
| } |
| } |
| return c.map(callback); |
| } |
| to_fast(); |
| to_fast(); |
| %OptimizeFunctionOnNextCall(to_fast); |
| var output = to_fast(); |
| %HasObjectElements(output); |
| assertEquals(975, result); |
| assertEquals("11hello", output[10]); |
| assertOptimized(to_fast); |
| })(); |
| |
| // TurboFan specializes on number results, ensure the code path is |
| // tested. |
| (function() { |
| var a = [1, 2, 3]; |
| function double_results() { |
| // TurboFan recognizes the result is a double. |
| var callback = v => v + 0.5; |
| return a.map(callback); |
| } |
| double_results(); |
| double_results(); |
| %OptimizeFunctionOnNextCall(double_results); |
| double_results(); |
| assertEquals(1.5, double_results()[0]); |
| })(); |
| |
| // TurboFan specializes on non-number results, ensure the code path is |
| // tested. |
| (function() { |
| var a = [1, 2, 3]; |
| function string_results() { |
| // TurboFan recognizes the result is a string. |
| var callback = v => "hello" + v.toString(); |
| return a.map(callback); |
| } |
| string_results(); |
| string_results(); |
| %OptimizeFunctionOnNextCall(string_results); |
| string_results(); |
| assertEquals("hello1", string_results()[0]); |
| })(); |
| |
| // Verify holes are not visited. |
| (() => { |
| const a = [1, 2, , 3, 4]; |
| let callback_values = []; |
| function withHoles() { |
| callback_values = []; |
| return a.map(v => { |
| callback_values.push(v); |
| return v; |
| }); |
| } |
| withHoles(); |
| withHoles(); |
| %OptimizeFunctionOnNextCall(withHoles); |
| assertArrayEquals([1, 2, , 3, 4], withHoles()); |
| assertArrayEquals([1, 2, 3, 4], callback_values); |
| })(); |
| |
| (() => { |
| const a = [1.5, 2.5, , 3.5, 4.5]; |
| let callback_values = []; |
| function withHoles() { |
| callback_values = []; |
| return a.map(v => { |
| callback_values.push(v); |
| return v; |
| }); |
| } |
| withHoles(); |
| withHoles(); |
| %OptimizeFunctionOnNextCall(withHoles); |
| assertArrayEquals([1.5, 2.5, , 3.5, 4.5], withHoles()); |
| assertArrayEquals([1.5, 2.5, 3.5, 4.5], callback_values); |
| })(); |
| |
| // 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.map(x => x * 2, 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); |
| })(); |
| |
| // Messing with the Array species constructor causes deoptimization. |
| (function() { |
| var result = 0; |
| var a = [1,2,3]; |
| var species_breakage = function() { |
| var callback = function(v,i,o) { |
| result += v; |
| return v; |
| } |
| a.map(callback); |
| } |
| species_breakage(); |
| species_breakage(); |
| %OptimizeFunctionOnNextCall(species_breakage); |
| species_breakage(); |
| a.constructor = {}; |
| a.constructor[Symbol.species] = function() {}; |
| species_breakage(); |
| assertUnoptimized(species_breakage); |
| assertEquals(24, result); |
| })(); |
| |
| ///////////////////////////////////////////////////////////////////////// |
| // |
| // Any tests added below species_breakage won't test optimized map calls |
| // because the array species constructor change disables inlining of |
| // Array.prototype.map across the isolate. |
| // |
| ///////////////////////////////////////////////////////////////////////// |