v8/test/mjsunit/optimized-map.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

553 lines
13 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 --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.
//
/////////////////////////////////////////////////////////////////////////