90c3a2d54b
This CL contains a few pieces: - A new mechanism to create "BuiltinContinuation" checkpoints in TurboFan graphs, which--when triggered--swizzle the values in the the FrameState to be parameters to a typically TF-generated builtin that resumes execution to finish the slow-case functionality. - Continuation builtins that have special handling in the deoptimizer and their own new frame type to ensure that the values they need to begin executing can be stashed away and restored immediately before the builtin is called via a trampoline that runs when the continuation builtin's frame execution resumes. - An implementation of Array.prototype.forEach in TurboFan that can be used to inline it. The inlined forEach implementation uses the checkpoints mechanism described above to deopt in the middle of the forEach in the cases that optimization invariants are violated. There is a slightly different continuation stub for each deopt point in the forEach implementation to ensure the correct side-effects, i.e. that the deopt of the builtin isn't programmatically observable. Review-Url: https://codereview.chromium.org/2803853005 Cr-Commit-Position: refs/heads/master@{#45764}
314 lines
7.0 KiB
JavaScript
314 lines
7.0 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
|
|
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
lazyDeopt();
|
|
lazyDeopt();
|
|
%OptimizeFunctionOnNextCall(lazyDeopt);
|
|
lazyDeopt();
|
|
assertDoesNotThrow(lazyDeopt.bind(this, true));
|
|
assertTrue(caught);
|
|
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);
|
|
}
|
|
};
|
|
var o = [1,2,3];
|
|
b.forEach(sum);
|
|
}
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
})();
|