v8/test/mjsunit/setters-on-elements.js
mvstanton@chromium.org cec8548d0e Correct handling of arrays with callbacks in the prototype chain.
Our generic KeyedStoreIC doesn't handle the case when a callback is
set on array elements in the prototype chain of the object, nor do
we recognize that we need to avoid the monomorphic case if these
callbacks exist.

This CL addresses the issue by looking for dictionary elements in
the prototype chain on IC misses and crankshaft element store
instructions. When found, the generic IC is used. The generic IC is
changed to go to the runtime in this case too.

In general, keyed loads are immune from this problem because they
won't return the hole: discovery of the hole goes to the runtime where
the callback will be found in the prototype chain. Double array loads
in crankshaft can return the hole but only if the prototype chain is
unaltered (we will catch such alterations).

Includes the following patch as well (already reviewed by bmeurer):
Performance regression found in test regress-2185-2.js. The problem was
that the bailout method for TransitionAndStoreStub was not performing
the appropriate transition.

(Review URL for the ElementsTransitionAndStoreIC_Miss change:
https://codereview.chromium.org/26911007)

R=danno@chromium.org

Review URL: https://codereview.chromium.org/35413006

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@17525 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2013-11-06 15:45:43 +00:00

200 lines
5.4 KiB
JavaScript

// Copyright 2011 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --allow-natives-syntax --max-opt-count=100 --noalways-opt
// We specify max-opt-count because we opt/deopt the same function many
// times.
// It's nice to run this in other browsers too.
var standalone = false;
if (standalone) {
assertTrue = function(val) {
if (val != true) {
print("FAILURE");
}
}
assertFalse = function(val) {
if (val != false) {
print("FAILURE");
}
}
assertEquals = function(expected, val) {
if (expected !== val) {
print("FAILURE");
}
}
empty_func = function(name) { }
assertUnoptimized = empty_func;
assertOptimized = empty_func;
optimize = empty_func;
clearFunctionTypeFeedback = empty_func;
deoptimizeFunction = empty_func;
} else {
optimize = function(name) {
%OptimizeFunctionOnNextCall(name);
}
clearFunctionTypeFeedback = function(name) {
%ClearFunctionTypeFeedback(name);
}
deoptimizeFunction = function(name) {
%DeoptimizeFunction(name);
}
}
function base_setter_test(create_func, index, store_value) {
var calls = 0;
// Testcase: setter in prototype chain
foo = function(a) { a[index] = store_value; }
var a = create_func();
var ap = [];
ap.__defineSetter__(index, function() { calls++; });
foo(a);
foo(a);
foo(a);
delete a[index];
assertEquals(0, calls);
a.__proto__ = ap;
foo(a);
assertEquals(1, calls);
optimize(foo);
foo(a);
assertEquals(2, calls);
assertOptimized(foo);
// Testcase: setter added on prototype chain object already in place.
clearFunctionTypeFeedback(foo);
deoptimizeFunction(foo);
clearFunctionTypeFeedback(foo);
calls = 0;
a = create_func();
var apap = [];
a.__proto__ = apap;
foo(a);
foo(a);
foo(a);
delete a[index];
apap.__defineSetter__(index, function() { calls++; });
foo(a);
foo(a);
foo(a);
assertEquals(3, calls);
// Testcase: setter "deep" in prototype chain.
clearFunctionTypeFeedback(foo);
deoptimizeFunction(foo);
clearFunctionTypeFeedback(foo);
calls = 0;
a = create_func();
var ap2 = [];
a.__proto__ = ap2;
foo(a);
foo(a);
foo(a);
delete a[index];
assertEquals(0, calls);
ap2.__proto__ = ap; // "sneak" in a callback.
// The sneak case should be caught by unoptimized code too.
assertUnoptimized(foo);
foo(a);
foo(a);
foo(a);
assertEquals(3, calls);
// Testcase: setter added after optimization (feedback is monomorphic)
clearFunctionTypeFeedback(foo);
deoptimizeFunction(foo);
clearFunctionTypeFeedback(foo);
calls = 0;
a = create_func();
ap2 = [];
a.__proto__ = ap2;
foo(a);
foo(a);
foo(a);
optimize(foo);
foo(a);
assertOptimized(foo);
delete a[index];
ap2.__proto__ = ap;
foo(a);
assertUnoptimized(foo); // map shape change should deopt foo.
assertEquals(1, calls);
// Testcase: adding additional setters to a prototype chain that already has
// one shouldn't deopt anything. (ie, we aren't changing the map shape).
clearFunctionTypeFeedback(foo);
calls = 0;
a = create_func();
a.__proto__ = ap2;
bar = function(a) { a[index+1] = store_value; }
bar(a);
bar(a);
bar(a); // store should be generic
optimize(bar);
bar(a);
assertOptimized(bar);
assertEquals(0, calls);
delete a[index+1];
ap2.__defineSetter__(index+1, function() { calls++; });
bar(a);
assertOptimized(bar);
assertEquals(1, calls);
}
// Verify that map transitions don't confuse us.
create_func_smi = function() { return [,,,,,,5]; }
create_func_double = function() { return [0,,3.2,,,,5.5]; }
create_func_fast = function() { return [,,,,,,true]; }
create_func_dictionary = function() { var a = []; a.length = 100000; return a; }
var cf = [create_func_smi,
create_func_double,
create_func_fast,
create_func_dictionary];
var values = [3, 3.5, true];
for(var c = 0; c < 3; c++) {
for(var s = 0; s < 3; s++) {
base_setter_test(cf[c], 0, values[s]);
base_setter_test(cf[c], 1, values[s]);
}
}