|  | // 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 --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. | 
|  |  | 
|  | // Early exit from some functions properly. | 
|  | (() => { | 
|  | const a = [1, 2, 3, 4, 5]; | 
|  | let result = 0; | 
|  | function earlyExit() { | 
|  | return a.some(v => { | 
|  | result += v; | 
|  | return v > 2; | 
|  | }); | 
|  | } | 
|  | %PrepareFunctionForOptimization(earlyExit); | 
|  | assertTrue(earlyExit()); | 
|  | earlyExit(); | 
|  | %OptimizeFunctionOnNextCall(earlyExit); | 
|  | assertTrue(earlyExit()); | 
|  | assertEquals(18, result); | 
|  | })(); | 
|  |  | 
|  | // Soft-deopt plus early exit. | 
|  | (() => { | 
|  | const a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; | 
|  | let result = 0; | 
|  | function softyPlusEarlyExit(deopt) { | 
|  | return a.some(v => { | 
|  | result += v; | 
|  | if (v === 4 && deopt) { | 
|  | a.abc = 25; | 
|  | } | 
|  | return v > 7; | 
|  | }); | 
|  | } | 
|  | %PrepareFunctionForOptimization(softyPlusEarlyExit); | 
|  | assertTrue(softyPlusEarlyExit(false)); | 
|  | softyPlusEarlyExit(false); | 
|  | %OptimizeFunctionOnNextCall(softyPlusEarlyExit); | 
|  | assertTrue(softyPlusEarlyExit(true)); | 
|  | assertEquals(36*3, result); | 
|  | })(); | 
|  |  | 
|  | // Soft-deopt synced with early exit, which forces the lazy deoptimization | 
|  | // continuation handler to exit. | 
|  | (() => { | 
|  | const a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; | 
|  | let called_values = []; | 
|  | function softyPlusEarlyExit(deopt) { | 
|  | called_values = []; | 
|  | return a.some(v => { | 
|  | called_values.push(v); | 
|  | if (v === 4 && deopt) { | 
|  | a.abc = 25; | 
|  | return true; | 
|  | } | 
|  | return v > 7; | 
|  | }); | 
|  | } | 
|  | %PrepareFunctionForOptimization(softyPlusEarlyExit); | 
|  | assertTrue(softyPlusEarlyExit(false)); | 
|  | assertArrayEquals([1, 2, 3, 4, 5, 6, 7, 8], called_values); | 
|  | softyPlusEarlyExit(false); | 
|  | %OptimizeFunctionOnNextCall(softyPlusEarlyExit); | 
|  | assertTrue(softyPlusEarlyExit(true)); | 
|  | assertArrayEquals([1, 2, 3, 4], called_values); | 
|  | })(); | 
|  |  | 
|  | // Unknown field access leads to soft-deopt unrelated to some, 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.some((v, i) => { | 
|  | if (i === 13 && deopt) { | 
|  | a.abc = 25; | 
|  | } | 
|  | result += v; | 
|  | return false; | 
|  | }); | 
|  | } | 
|  | %PrepareFunctionForOptimization(eagerDeoptInCalled); | 
|  | eagerDeoptInCalled(); | 
|  | eagerDeoptInCalled(); | 
|  | %OptimizeFunctionOnNextCall(eagerDeoptInCalled); | 
|  | eagerDeoptInCalled(); | 
|  | assertFalse(eagerDeoptInCalled(true)); | 
|  | eagerDeoptInCalled(); | 
|  | assertEquals(1625, 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.some((v,i) => { | 
|  | called_values.push(v); | 
|  | a.length = (i === 5 && deopt) ? 8 : 10; | 
|  | return false; | 
|  | }); | 
|  | } | 
|  | %PrepareFunctionForOptimization(eagerDeoptInCalled); | 
|  | assertFalse(eagerDeoptInCalled()); | 
|  | assertArrayEquals([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], called_values); | 
|  | eagerDeoptInCalled(); | 
|  | %OptimizeFunctionOnNextCall(eagerDeoptInCalled); | 
|  | assertFalse(eagerDeoptInCalled()); | 
|  | assertFalse(eagerDeoptInCalled(true)); | 
|  | assertArrayEquals([1, 2, 3, 4, 5, 6, 7, 8], 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.some((v, i) => { | 
|  | if (i === 3 && deopt) { | 
|  | a[3] = 100; | 
|  | %DeoptimizeNow(); | 
|  | } | 
|  | return false; | 
|  | }); | 
|  | } | 
|  | %PrepareFunctionForOptimization(lazyChanger); | 
|  | assertFalse(lazyChanger()); | 
|  | lazyChanger(); | 
|  | %OptimizeFunctionOnNextCall(lazyChanger); | 
|  | assertFalse(lazyChanger(true)); | 
|  | assertFalse(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.some((v, i) => { | 
|  | if (i === 3 && deopt) { | 
|  | %DeoptimizeNow(); | 
|  | } | 
|  | return false; | 
|  | }); | 
|  | } | 
|  | %PrepareFunctionForOptimization(lazyChanger); | 
|  | assertFalse(lazyChanger()); | 
|  | lazyChanger(); | 
|  | %OptimizeFunctionOnNextCall(lazyChanger); | 
|  | assertFalse(lazyChanger(true)); | 
|  | assertFalse(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.every((v, i) => { | 
|  | if (i === 2 && deopt) { | 
|  | a[3] = 100; | 
|  | %DeoptimizeNow(); | 
|  | } | 
|  | return false; | 
|  | }); | 
|  | } | 
|  | %PrepareFunctionForOptimization(lazyChanger); | 
|  | assertFalse(lazyChanger()); | 
|  | lazyChanger(); | 
|  | %OptimizeFunctionOnNextCall(lazyChanger); | 
|  | assertFalse(lazyChanger(true)); | 
|  | assertFalse(lazyChanger()); | 
|  | })(); | 
|  |  | 
|  | // Escape analyzed array | 
|  | (() => { | 
|  | let result = 0; | 
|  | function eagerDeoptInCalled(deopt) { | 
|  | const a_noescape = [0, 1, 2, 3, 4, 5]; | 
|  | a_noescape.some((v, i) => { | 
|  | result += v | 0; | 
|  | if (i === 13 && deopt) { | 
|  | a_noescape.length = 25; | 
|  | } | 
|  | return false; | 
|  | }); | 
|  | } | 
|  | %PrepareFunctionForOptimization(eagerDeoptInCalled); | 
|  | 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.some((v, i) => { | 
|  | result += i; | 
|  | if (i === 13 && deopt) { | 
|  | %DeoptimizeNow(); | 
|  | } | 
|  | return false; | 
|  | }); | 
|  | } | 
|  | %PrepareFunctionForOptimization(lazyDeopt); | 
|  | 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.some(callback); | 
|  | } | 
|  | %PrepareFunctionForOptimization(lazyDeopt); | 
|  | lazyDeopt(); | 
|  | lazyDeopt(); | 
|  | %OptimizeFunctionOnNextCall(lazyDeopt); | 
|  | lazyDeopt(); | 
|  | lazyDeopt(true); | 
|  | lazyDeopt(); | 
|  | assertEquals(1500, result); | 
|  | })(); | 
|  |  | 
|  | // Call to a.some 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.some((v, i) => { | 
|  | if (i === 1 && deopt) { | 
|  | throw("a"); | 
|  | } | 
|  | return false; | 
|  | }); | 
|  | } catch (e) { | 
|  | caught = true; | 
|  | } | 
|  | } | 
|  | %PrepareFunctionForOptimization(lazyDeopt); | 
|  | lazyDeopt(); | 
|  | lazyDeopt(); | 
|  | %OptimizeFunctionOnNextCall(lazyDeopt); | 
|  | lazyDeopt(); | 
|  | assertDoesNotThrow(() => lazyDeopt(true)); | 
|  | assertTrue(caught); | 
|  | lazyDeopt(); | 
|  | })(); | 
|  |  | 
|  | // Call to a.some 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.some(callback); | 
|  | } catch (e) { | 
|  | caught = true; | 
|  | } | 
|  | } | 
|  | %PrepareFunctionForOptimization(lazyDeopt); | 
|  | lazyDeopt(); | 
|  | lazyDeopt(); | 
|  | %OptimizeFunctionOnNextCall(lazyDeopt); | 
|  | lazyDeopt(); | 
|  | assertDoesNotThrow(() => lazyDeopt(true)); | 
|  | assertTrue(caught); | 
|  | lazyDeopt(); | 
|  | })(); | 
|  |  | 
|  | // Call to a.some 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 false; | 
|  | } | 
|  | %NeverOptimizeFunction(callback); | 
|  | let result = 0; | 
|  | try { | 
|  | result = a.some(callback); | 
|  | } catch (e) { | 
|  | assertEquals("some exception", e); | 
|  | result = "nope"; | 
|  | } | 
|  | return result; | 
|  | } | 
|  | %PrepareFunctionForOptimization(lazyDeopt); | 
|  | assertEquals(false, lazyDeopt(false)); | 
|  | assertEquals(false, lazyDeopt(false)); | 
|  | assertEquals("nope", lazyDeopt(true)); | 
|  | assertEquals("nope", lazyDeopt(true)); | 
|  | %OptimizeFunctionOnNextCall(lazyDeopt); | 
|  | assertEquals(false, lazyDeopt(false)); | 
|  | assertEquals("nope", lazyDeopt(true)); | 
|  | })(); | 
|  |  | 
|  | // An error generated inside the callback includes some in it's | 
|  | // stack trace. | 
|  | (() => { | 
|  | const re = /Array\.some/; | 
|  | function lazyDeopt(deopt) { | 
|  | const b = [1, 2, 3]; | 
|  | let result = 0; | 
|  | b.some((v, i) => { | 
|  | result += v; | 
|  | if (i === 1) { | 
|  | const e = new Error(); | 
|  | assertTrue(re.exec(e.stack) !== null); | 
|  | } | 
|  | return false; | 
|  | }); | 
|  | } | 
|  | %PrepareFunctionForOptimization(lazyDeopt); | 
|  | lazyDeopt(); | 
|  | lazyDeopt(); | 
|  | %OptimizeFunctionOnNextCall(lazyDeopt); | 
|  | lazyDeopt(); | 
|  | })(); | 
|  |  | 
|  | // An error generated inside a non-inlined callback function also | 
|  | // includes some in it's stack trace. | 
|  | (() => { | 
|  | const re = /Array\.some/; | 
|  | 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.some(callback); | 
|  | return did_assert_error; | 
|  | } | 
|  | %PrepareFunctionForOptimization(lazyDeopt); | 
|  | lazyDeopt(); | 
|  | lazyDeopt(); | 
|  | %OptimizeFunctionOnNextCall(lazyDeopt); | 
|  | assertTrue(lazyDeopt()); | 
|  | })(); | 
|  |  | 
|  | // An error generated inside a recently deoptimized callback function | 
|  | // includes some in it's stack trace. | 
|  | (() => { | 
|  | const re = /Array\.some/; | 
|  | function lazyDeopt(deopt) { | 
|  | const b = [1, 2, 3]; | 
|  | let did_assert_error = false; | 
|  | let result = 0; | 
|  | b.some((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; | 
|  | } | 
|  | %PrepareFunctionForOptimization(lazyDeopt); | 
|  | 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 some call stack. | 
|  | (() => { | 
|  | const re = /Array\.some/; | 
|  | const a = [1, 2, 3]; | 
|  | let result = 0; | 
|  | function lazyDeopt() { | 
|  | a.some((v, i) => { | 
|  | result += i; | 
|  | if (i === 1) { | 
|  | %DeoptimizeFunction(lazyDeopt); | 
|  | throw new Error(); | 
|  | } | 
|  | return false; | 
|  | }); | 
|  | } | 
|  | %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); | 
|  | } | 
|  | })(); | 
|  |  | 
|  | // Messing with the Array prototype causes deoptimization. | 
|  | (() => { | 
|  | const a = [1, 2, 3]; | 
|  | let result = 0; | 
|  | function prototypeChanged() { | 
|  | a.some((v, i) => { | 
|  | result += v; | 
|  | return false; | 
|  | }); | 
|  | } | 
|  | %PrepareFunctionForOptimization(prototypeChanged); | 
|  | prototypeChanged(); | 
|  | prototypeChanged(); | 
|  | %OptimizeFunctionOnNextCall(prototypeChanged); | 
|  | prototypeChanged(); | 
|  | a.constructor = {}; | 
|  | prototypeChanged(); | 
|  | assertUnoptimized(prototypeChanged); | 
|  | assertEquals(24, result); | 
|  | })(); | 
|  |  | 
|  | // Verify holes are skipped. | 
|  | (() => { | 
|  | const a = [1, 2, , 3, 4]; | 
|  | function withHoles() { | 
|  | const callback_values = []; | 
|  | a.some(v => { | 
|  | callback_values.push(v); | 
|  | return false; | 
|  | }); | 
|  | 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.some(v => { | 
|  | callback_values.push(v); | 
|  | return false; | 
|  | }); | 
|  | return callback_values; | 
|  | } | 
|  | %PrepareFunctionForOptimization(withHoles); | 
|  | withHoles(); | 
|  | withHoles(); | 
|  | %OptimizeFunctionOnNextCall(withHoles); | 
|  | assertArrayEquals([1.5, 2.5, 3.5, 4.5], withHoles()); | 
|  | })(); | 
|  |  | 
|  | // Handle callback is not callable. | 
|  | (() => { | 
|  | const a = [1, 2, 3, 4, 5]; | 
|  | function notCallable() { | 
|  | return a.some(undefined); | 
|  | } | 
|  | %PrepareFunctionForOptimization(notCallable); | 
|  |  | 
|  | assertThrows(notCallable, TypeError); | 
|  | try { notCallable(); } catch(e) { } | 
|  | %OptimizeFunctionOnNextCall(notCallable); | 
|  | assertThrows(notCallable, TypeError); | 
|  | })(); |